use crate::session_context::SessionContext;
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessMode {
ReadOnly,
Mutating,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum RiskTier {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApprovalRequirement {
None,
HumanApprovalPerCall,
}
#[derive(Debug, Clone)]
pub struct CapabilityDescriptor {
pub name: &'static str,
pub description: &'static str,
pub access_mode: AccessMode,
pub risk_tier: RiskTier,
pub approval: ApprovalRequirement,
pub input_schema: Value,
pub output_schema: Value,
pub availability: fn(&SessionContext) -> bool,
}
use super::registry_data::all_descriptors;
pub struct Manifest {
descriptors: Vec<CapabilityDescriptor>,
}
impl Manifest {
pub fn all() -> Self {
Self {
descriptors: all_descriptors(),
}
}
pub fn get(&self, name: &str) -> Option<&CapabilityDescriptor> {
self.descriptors.iter().find(|d| d.name == name)
}
pub fn available(&self, ctx: &SessionContext) -> Vec<&CapabilityDescriptor> {
self.descriptors
.iter()
.filter(|d| (d.availability)(ctx))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn ctx() -> SessionContext {
SessionContext::new("s", PathBuf::from("/tmp"), "ollama", "m", false)
}
#[test]
fn all_ten_descriptors_present() {
assert_eq!(Manifest::all().descriptors.len(), 10);
}
#[test]
fn get_finds_by_name() {
let manifest = Manifest::all();
assert_eq!(manifest.get("repo.read").unwrap().risk_tier, RiskTier::Low);
assert!(manifest.get("does.not.exist").is_none());
}
#[test]
fn command_execute_requires_approval_and_is_mutating() {
let manifest = Manifest::all();
let descriptor = manifest.get("command.execute").unwrap();
assert_eq!(descriptor.access_mode, AccessMode::Mutating);
assert_eq!(
descriptor.approval,
ApprovalRequirement::HumanApprovalPerCall
);
assert_eq!(descriptor.risk_tier, RiskTier::High);
}
#[test]
fn read_only_capabilities_need_no_approval() {
let manifest = Manifest::all();
for name in [
"repo.tree",
"repo.read",
"repo.search",
"git.status",
"git.diff",
] {
let descriptor = manifest.get(name).unwrap();
assert_eq!(descriptor.access_mode, AccessMode::ReadOnly);
assert_eq!(descriptor.approval, ApprovalRequirement::None);
}
}
#[test]
fn every_descriptor_has_object_schemas() {
for descriptor in Manifest::all().descriptors {
assert!(descriptor.input_schema.is_object());
assert!(descriptor.output_schema.is_object());
}
}
#[test]
fn available_filters_by_session_context() {
let manifest = Manifest::all();
let available = manifest.available(&ctx());
assert!(available.len() <= 10);
assert!(available.iter().any(|d| d.name == "repo.read"));
}
}