Skip to main content

rill_runtime/handler/
mod.rs

1//! Handler adapters for the runtime engine.
2//!
3//! The runtime delegates capability execution to an [`InvokeHandler`](crate::server::InvokeHandler). Built-in
4//! handlers live in [`builtin`]; sandboxed WASM handlers live in [`wasm`] behind
5//! the `wasm` feature flag.
6//!
7//! Both submodules are `pub(crate)` after the 1.0 dual-module-path cleanup.
8//! Public types and constants are re-exported from [`crate`].
9
10pub(crate) mod builtin;
11#[cfg(feature = "wasm")]
12pub(crate) mod wasm;
13
14use serde::Serialize;
15
16/// Identity reported by the runtime in IPC v2 handshake responses.
17///
18/// `effective_capabilities` is the intersection of the model pack and handler
19/// pack capability lists. Only these capabilities can be invoked.
20#[derive(Debug, Clone, Serialize)]
21pub struct HandlerIdentity {
22    pub handler_id: String,
23    pub handler_version: String,
24    pub handler_api_version: u32,
25    pub effective_capabilities: Vec<String>,
26}
27
28/// Errors that arise while loading or preparing a handler for execution.
29#[derive(Debug, thiserror::Error)]
30#[non_exhaustive]
31pub enum HandlerLoadError {
32    #[error("handler pack error: {0}")]
33    Pack(#[from] crate::handler_package::HandlerPackError),
34    #[error("handler does not cover all model capabilities: missing {missing:?}")]
35    CapabilityMissing { missing: Vec<String> },
36    #[error("handler failed to initialize: {0}")]
37    Init(String),
38    #[error("guest metadata does not match signed manifest: {0}")]
39    MetadataMismatch(String),
40}
41
42/// Computes the effective capability set: the intersection of model and handler
43/// capabilities. Returns an error if the handler does not cover every model
44/// capability (first version rejects silent capability loss).
45pub fn effective_capabilities(
46    model: &[String],
47    handler: &[String],
48) -> Result<Vec<String>, HandlerLoadError> {
49    let mut model_sorted = model.to_vec();
50    model_sorted.sort();
51    let mut handler_sorted = handler.to_vec();
52    handler_sorted.sort();
53
54    let effective: Vec<String> = model_sorted
55        .iter()
56        .filter(|capability| handler_sorted.binary_search(capability).is_ok())
57        .cloned()
58        .collect();
59
60    if effective.len() != model.len() {
61        let missing: Vec<String> = model_sorted
62            .iter()
63            .filter(|capability| handler_sorted.binary_search(capability).is_err())
64            .cloned()
65            .collect();
66        return Err(HandlerLoadError::CapabilityMissing { missing });
67    }
68    Ok(effective)
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn effective_capabilities_intersect() {
77        let model = vec!["a".into(), "b".into()];
78        let handler = vec!["a".into(), "b".into(), "c".into()];
79        let result = effective_capabilities(&model, &handler).unwrap();
80        assert_eq!(result, vec!["a", "b"]);
81    }
82
83    #[test]
84    fn effective_capabilities_reject_missing() {
85        let model = vec!["a".into(), "b".into()];
86        let handler = vec!["a".into()];
87        let error = effective_capabilities(&model, &handler).unwrap_err();
88        assert!(
89            matches!(error, HandlerLoadError::CapabilityMissing { missing } if missing == vec!["b"])
90        );
91    }
92}