use chio_core::capability::scope::MonetaryAmount;
use serde::{Deserialize, Serialize};
pub const SKILL_GRANT_SCHEMA: &str = "chio.skill-grant.v1";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillGrant {
pub schema: String,
pub skill_id: String,
pub skill_version: String,
pub authorized_steps: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_executions: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budget_envelope: Option<MonetaryAmount>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_duration_secs: Option<u64>,
#[serde(default = "default_strict_ordering")]
pub strict_ordering: bool,
}
fn default_strict_ordering() -> bool {
true
}
impl SkillGrant {
pub fn new(skill_id: String, skill_version: String, authorized_steps: Vec<String>) -> Self {
Self {
schema: SKILL_GRANT_SCHEMA.to_string(),
skill_id,
skill_version,
authorized_steps,
max_executions: None,
budget_envelope: None,
max_duration_secs: None,
strict_ordering: true,
}
}
#[must_use]
pub fn authorizes_step(&self, server_id: &str, tool_name: &str) -> bool {
let key = format!("{server_id}:{tool_name}");
self.authorized_steps.contains(&key)
}
#[must_use]
pub fn is_step_in_order(&self, step_index: usize, completed_steps: usize) -> bool {
if !self.strict_ordering {
return true;
}
step_index == completed_steps
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn skill_grant_roundtrip() {
let grant = SkillGrant::new(
"search-and-summarize".to_string(),
"1.0.0".to_string(),
vec![
"search-server:search".to_string(),
"llm-server:summarize".to_string(),
],
);
let json = serde_json::to_string(&grant).unwrap();
let deserialized: SkillGrant = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.skill_id, "search-and-summarize");
assert_eq!(deserialized.authorized_steps.len(), 2);
assert!(deserialized.strict_ordering);
}
#[test]
fn authorizes_step() {
let grant = SkillGrant::new(
"s1".to_string(),
"1.0".to_string(),
vec!["srv:tool_a".to_string(), "srv:tool_b".to_string()],
);
assert!(grant.authorizes_step("srv", "tool_a"));
assert!(grant.authorizes_step("srv", "tool_b"));
assert!(!grant.authorizes_step("srv", "tool_c"));
}
#[test]
fn strict_ordering() {
let grant = SkillGrant::new(
"s1".to_string(),
"1.0".to_string(),
vec!["a:t1".to_string(), "a:t2".to_string()],
);
assert!(grant.is_step_in_order(0, 0)); assert!(grant.is_step_in_order(1, 1)); assert!(!grant.is_step_in_order(1, 0)); }
#[test]
fn relaxed_ordering() {
let mut grant = SkillGrant::new(
"s1".to_string(),
"1.0".to_string(),
vec!["a:t1".to_string(), "a:t2".to_string()],
);
grant.strict_ordering = false;
assert!(grant.is_step_in_order(1, 0)); }
}