skill_context/
lib.rs

1//! Skill execution context management.
2//!
3//! This crate provides types and utilities for defining and managing
4//! execution contexts for skill-engine skills. An execution context
5//! defines the complete environment in which a skill's tools execute,
6//! including:
7//!
8//! - File and directory mounts
9//! - Environment variables
10//! - Secrets and credentials
11//! - Resource limits (CPU, memory, network)
12//! - Runtime-specific overrides
13//!
14//! # Core Concepts
15//!
16//! ## Execution Context
17//!
18//! An [`ExecutionContext`] is the central type that combines all configuration
19//! needed to run a skill. Contexts can inherit from other contexts, allowing
20//! for a hierarchy of configurations (e.g., base → development → production).
21//!
22//! ```rust
23//! use skill_context::{ExecutionContext, EnvironmentConfig, ResourceConfig};
24//!
25//! let context = ExecutionContext::new("my-context", "My Context")
26//!     .with_description("A production context")
27//!     .with_environment(
28//!         EnvironmentConfig::new()
29//!             .with_var("LOG_LEVEL", "info")
30//!             .with_passthrough_prefix("AWS_")
31//!     )
32//!     .with_resources(
33//!         ResourceConfig::new()
34//!             .with_memory_limit("1g")
35//!             .with_network_enabled()
36//!             .with_timeout(300)
37//!     )
38//!     .with_tag("production");
39//! ```
40//!
41//! ## Mounts
42//!
43//! [`Mount`]s define files and directories that should be accessible
44//! within the execution environment:
45//!
46//! ```rust
47//! use skill_context::Mount;
48//!
49//! let data_mount = Mount::directory("data", "/host/data", "/app/data")
50//!     .as_read_write()
51//!     .with_description("Application data directory");
52//!
53//! let config_mount = Mount::config_file(
54//!     "app-config",
55//!     r#"
56//!     [api]
57//!     endpoint = "${API_ENDPOINT}"
58//!     "#,
59//!     "/etc/app/config.toml"
60//! );
61//! ```
62//!
63//! ## Secrets
64//!
65//! The [`SecretsConfig`] type manages secret definitions and providers:
66//!
67//! ```rust
68//! use skill_context::{SecretsConfig, SecretDefinition};
69//!
70//! let secrets = SecretsConfig::new()
71//!     .with_required_env_secret("api-key", "API_KEY", "API authentication key")
72//!     .with_required_file_secret("db-password", "/run/secrets/db", "Database password");
73//! ```
74//!
75//! ## Resources
76//!
77//! [`ResourceConfig`] defines limits and capabilities:
78//!
79//! ```rust
80//! use skill_context::{ResourceConfig, NetworkConfig};
81//!
82//! let resources = ResourceConfig::new()
83//!     .with_cpu_limit("2")
84//!     .with_memory_limit("1g")
85//!     .with_network(
86//!         NetworkConfig::enabled()
87//!             .allow_host("api.example.com")
88//!             .allow_host("*.amazonaws.com")
89//!     )
90//!     .with_timeout(300);
91//! ```
92//!
93//! # Features
94//!
95//! - `vault` - Enable HashiCorp Vault secret provider
96//! - `aws-secrets` - Enable AWS Secrets Manager provider
97//! - `azure-keyvault` - Enable Azure Key Vault provider
98//! - `gcp-secrets` - Enable GCP Secret Manager provider
99
100#![warn(missing_docs)]
101#![warn(rustdoc::missing_crate_level_docs)]
102
103pub mod context;
104pub mod environment;
105pub mod inheritance;
106pub mod mounts;
107pub mod providers;
108pub mod resources;
109pub mod runtime;
110pub mod secrets;
111pub mod storage;
112
113// Re-export main types at crate root
114pub use context::{ContextMetadata, ExecutionContext};
115pub use environment::{
116    EnvFileRef, EnvValue, EnvironmentConfig, GeneratedValue, SecretRef,
117};
118pub use mounts::{Mount, MountType};
119pub use resources::{
120    CpuConfig, ExecutionLimits, FilesystemConfig, MemoryConfig, NetworkConfig,
121    RateLimit, ResourceConfig,
122};
123pub use runtime::{DockerOverrides, NativeOverrides, RuntimeOverrides, WasmOverrides};
124pub use secrets::{
125    ExternalSecretProvider, SecretDefinition, SecretFileFormat, SecretInjectionTarget,
126    SecretProviderConfig, SecretsConfig,
127};
128
129// Re-export inheritance types
130pub use inheritance::{
131    merge_environments, merge_mounts, merge_resources, merge_secrets, resolve_context,
132    ContextResolver,
133};
134
135// Re-export storage types
136pub use storage::{BackupInfo, ContextIndex, ContextIndexEntry, ContextStorage};
137
138// Re-export provider types
139pub use providers::{
140    EnvironmentProvider, FileProvider, KeychainProvider, SecretManager, SecretProvider,
141    SecretValue,
142};
143
144/// Error types for the skill-context crate.
145pub mod error {
146    use thiserror::Error;
147
148    /// Errors that can occur during context operations.
149    #[derive(Debug, Error)]
150    pub enum ContextError {
151        /// Context not found.
152        #[error("Context not found: {0}")]
153        NotFound(String),
154
155        /// Context already exists.
156        #[error("Context already exists: {0}")]
157        AlreadyExists(String),
158
159        /// Invalid context configuration.
160        #[error("Invalid context configuration: {0}")]
161        InvalidConfig(String),
162
163        /// Circular inheritance detected.
164        #[error("Circular inheritance detected: {0}")]
165        CircularInheritance(String),
166
167        /// Parent context not found.
168        #[error("Parent context not found: {0}")]
169        ParentNotFound(String),
170
171        /// Secret not found.
172        #[error("Secret not found: {0}")]
173        SecretNotFound(String),
174
175        /// Required secret not set.
176        #[error("Required secret not set: {0}")]
177        RequiredSecretNotSet(String),
178
179        /// Mount source not found.
180        #[error("Mount source not found: {0}")]
181        MountSourceNotFound(String),
182
183        /// Invalid mount configuration.
184        #[error("Invalid mount configuration: {0}")]
185        InvalidMount(String),
186
187        /// IO error.
188        #[error("IO error: {0}")]
189        Io(#[from] std::io::Error),
190
191        /// Serialization error.
192        #[error("Serialization error: {0}")]
193        Serialization(String),
194
195        /// Secret provider error.
196        #[error("Secret provider error: {0}")]
197        SecretProvider(String),
198    }
199
200    impl From<serde_json::Error> for ContextError {
201        fn from(e: serde_json::Error) -> Self {
202            Self::Serialization(e.to_string())
203        }
204    }
205
206    impl From<toml::de::Error> for ContextError {
207        fn from(e: toml::de::Error) -> Self {
208            Self::Serialization(e.to_string())
209        }
210    }
211
212    impl From<toml::ser::Error> for ContextError {
213        fn from(e: toml::ser::Error) -> Self {
214            Self::Serialization(e.to_string())
215        }
216    }
217}
218
219pub use error::ContextError;
220
221/// Result type for context operations.
222pub type Result<T> = std::result::Result<T, ContextError>;
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn test_full_context_creation() {
230        let context = ExecutionContext::new("test-context", "Test Context")
231            .with_description("A comprehensive test context")
232            .with_mount(
233                Mount::directory("data", "/host/data", "/app/data")
234                    .as_read_write()
235                    .with_description("Data directory"),
236            )
237            .with_mount(
238                Mount::tmpfs("temp", "/tmp", 100)
239                    .as_optional(),
240            )
241            .with_environment(
242                EnvironmentConfig::new()
243                    .with_var("LOG_LEVEL", "debug")
244                    .with_var("APP_ENV", "test")
245                    .with_passthrough_prefix("AWS_")
246                    .with_passthrough_var("PATH"),
247            )
248            .with_secrets(
249                SecretsConfig::new()
250                    .with_required_env_secret("api-key", "API_KEY", "API key for auth"),
251            )
252            .with_resources(
253                ResourceConfig::new()
254                    .with_cpu_limit("2")
255                    .with_memory_limit("1g")
256                    .with_network_enabled()
257                    .with_timeout(300),
258            )
259            .with_runtime_overrides(
260                RuntimeOverrides::new()
261                    .with_docker(
262                        DockerOverrides::new()
263                            .with_user("1000:1000")
264                            .with_no_new_privileges(),
265                    ),
266            )
267            .with_tag("test")
268            .with_tag("comprehensive");
269
270        // Verify structure
271        assert_eq!(context.id, "test-context");
272        assert_eq!(context.name, "Test Context");
273        assert_eq!(context.mounts.len(), 2);
274        assert_eq!(context.environment.variables.len(), 2);
275        assert!(!context.secrets.is_empty());
276        assert!(context.resources.cpu.is_some());
277        assert!(context.resources.memory.is_some());
278        assert!(context.resources.network.enabled);
279        assert!(context.runtime_overrides.is_some());
280        assert_eq!(context.metadata.tags.len(), 2);
281    }
282
283    #[test]
284    fn test_context_inheritance_setup() {
285        let base = ExecutionContext::new("base", "Base Context")
286            .with_environment(
287                EnvironmentConfig::new()
288                    .with_var("BASE_VAR", "base_value"),
289            );
290
291        let child = ExecutionContext::inheriting("child", "Child Context", "base")
292            .with_environment(
293                EnvironmentConfig::new()
294                    .with_var("CHILD_VAR", "child_value"),
295            );
296
297        assert!(!base.has_parent());
298        assert!(child.has_parent());
299        assert_eq!(child.inherits_from, Some("base".to_string()));
300    }
301
302    #[test]
303    fn test_full_serialization_roundtrip() {
304        let context = ExecutionContext::new("roundtrip-test", "Roundtrip Test")
305            .with_mount(Mount::directory("data", "/host", "/container"))
306            .with_environment(
307                EnvironmentConfig::new()
308                    .with_var("KEY", "value"),
309            )
310            .with_secrets(
311                SecretsConfig::new()
312                    .with_required_env_secret("secret", "SECRET_VAR", "A secret"),
313            )
314            .with_resources(
315                ResourceConfig::new()
316                    .with_memory_limit("512m"),
317            );
318
319        // JSON roundtrip
320        let json = serde_json::to_string_pretty(&context).unwrap();
321        let from_json: ExecutionContext = serde_json::from_str(&json).unwrap();
322        assert_eq!(context.id, from_json.id);
323        assert_eq!(context.mounts.len(), from_json.mounts.len());
324
325        // TOML roundtrip
326        let toml_str = toml::to_string_pretty(&context).unwrap();
327        let from_toml: ExecutionContext = toml::from_str(&toml_str).unwrap();
328        assert_eq!(context.id, from_toml.id);
329        assert_eq!(context.mounts.len(), from_toml.mounts.len());
330    }
331}