pinakes-core: add plugin pipeline; impl signature verification & dependency resolution

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ida98135cf868db0f5a46a64b8ac562366a6a6964
This commit is contained in:
raf 2026-03-08 14:23:02 +03:00
commit 4edda201e6
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
12 changed files with 2679 additions and 36 deletions

View file

@ -235,6 +235,54 @@ impl CapabilityEnforcer {
.unwrap_or(self.max_cpu_time_limit)
.min(self.max_cpu_time_limit)
}
/// Validate that a function call is allowed for a plugin's declared kinds.
///
/// Defense-in-depth: even though the pipeline filters by kind, this prevents
/// bugs from calling wrong functions on plugins. Returns `true` if allowed.
#[must_use]
pub fn validate_function_call(
&self,
plugin_kinds: &[String],
function_name: &str,
) -> bool {
match function_name {
// Lifecycle functions are always allowed
"initialize" | "shutdown" | "health_check" => true,
// MediaTypeProvider
"supported_media_types" | "can_handle" => {
plugin_kinds.iter().any(|k| k == "media_type")
},
// supported_types is shared by metadata_extractor and thumbnail_generator
"supported_types" => {
plugin_kinds
.iter()
.any(|k| k == "metadata_extractor" || k == "thumbnail_generator")
},
// MetadataExtractor
"extract_metadata" => {
plugin_kinds.iter().any(|k| k == "metadata_extractor")
},
// ThumbnailGenerator
"generate_thumbnail" => {
plugin_kinds.iter().any(|k| k == "thumbnail_generator")
},
// SearchBackend
"search" | "index_item" | "remove_item" | "get_stats" => {
plugin_kinds.iter().any(|k| k == "search_backend")
},
// EventHandler
"interested_events" | "handle_event" => {
plugin_kinds.iter().any(|k| k == "event_handler")
},
// ThemeProvider
"get_themes" | "load_theme" => {
plugin_kinds.iter().any(|k| k == "theme_provider")
},
// Unknown function names are not allowed
_ => false,
}
}
}
impl Default for CapabilityEnforcer {
@ -356,20 +404,70 @@ mod tests {
let mut caps = Capabilities::default();
// No limits specified - use defaults
// No limits specified, use the defaults
assert_eq!(enforcer.get_memory_limit(&caps), 100 * 1024 * 1024);
assert_eq!(enforcer.get_cpu_time_limit(&caps), 30_000);
// Plugin requests lower limits - use plugin's
// Plugin requests lower limits, use plugin's
caps.max_memory_bytes = Some(50 * 1024 * 1024);
caps.max_cpu_time_ms = Some(10_000);
assert_eq!(enforcer.get_memory_limit(&caps), 50 * 1024 * 1024);
assert_eq!(enforcer.get_cpu_time_limit(&caps), 10_000);
// Plugin requests higher limits - cap at system max
// Plugin requests higher limits, cap at system max
caps.max_memory_bytes = Some(200 * 1024 * 1024);
caps.max_cpu_time_ms = Some(60_000);
assert_eq!(enforcer.get_memory_limit(&caps), 100 * 1024 * 1024);
assert_eq!(enforcer.get_cpu_time_limit(&caps), 30_000);
}
#[test]
fn test_validate_function_call_lifecycle_always_allowed() {
let enforcer = CapabilityEnforcer::new();
let kinds = vec!["metadata_extractor".to_string()];
assert!(enforcer.validate_function_call(&kinds, "initialize"));
assert!(enforcer.validate_function_call(&kinds, "shutdown"));
assert!(enforcer.validate_function_call(&kinds, "health_check"));
}
#[test]
fn test_validate_function_call_metadata_extractor() {
let enforcer = CapabilityEnforcer::new();
let kinds = vec!["metadata_extractor".to_string()];
assert!(enforcer.validate_function_call(&kinds, "extract_metadata"));
assert!(enforcer.validate_function_call(&kinds, "supported_types"));
assert!(!enforcer.validate_function_call(&kinds, "search"));
assert!(!enforcer.validate_function_call(&kinds, "generate_thumbnail"));
assert!(!enforcer.validate_function_call(&kinds, "can_handle"));
}
#[test]
fn test_validate_function_call_multi_kind() {
let enforcer = CapabilityEnforcer::new();
let kinds =
vec!["media_type".to_string(), "metadata_extractor".to_string()];
assert!(enforcer.validate_function_call(&kinds, "can_handle"));
assert!(enforcer.validate_function_call(&kinds, "supported_media_types"));
assert!(enforcer.validate_function_call(&kinds, "extract_metadata"));
assert!(!enforcer.validate_function_call(&kinds, "search"));
}
#[test]
fn test_validate_function_call_unknown_function() {
let enforcer = CapabilityEnforcer::new();
let kinds = vec!["metadata_extractor".to_string()];
assert!(!enforcer.validate_function_call(&kinds, "unknown_func"));
assert!(!enforcer.validate_function_call(&kinds, ""));
}
#[test]
fn test_validate_function_call_shared_supported_types() {
let enforcer = CapabilityEnforcer::new();
let extractor = vec!["metadata_extractor".to_string()];
let generator = vec!["thumbnail_generator".to_string()];
let search = vec!["search_backend".to_string()];
assert!(enforcer.validate_function_call(&extractor, "supported_types"));
assert!(enforcer.validate_function_call(&generator, "supported_types"));
assert!(!enforcer.validate_function_call(&search, "supported_types"));
}
}