use hatchling_core::CapabilityName;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error("skill '{name}' refused: manifest is not signed")]
Unsigned {
name: String,
},
#[error("skill '{name}' refused: signature verification failed")]
BadSignature {
name: String,
},
#[error(
"skill '{name}' refused: manifest declares capabilities beyond user grants: {}",
list(.excess)
)]
OverPrivileged {
name: String,
excess: Vec<CapabilityName>,
},
#[error("skill '{name}' denied: capability '{capability}' was not declared in the manifest")]
UndeclaredCapability {
name: String,
capability: CapabilityName,
},
#[error("skill manifest invalid: {0}")]
InvalidManifest(String),
#[error("trust root invalid: {0}")]
InvalidTrustRoot(String),
#[error("skill '{id}' refused: code drift detected (expected {expected}, found {found})")]
IdentityDrift {
id: String,
expected: String,
found: String,
},
#[error("unknown skill: '{0}'")]
UnknownSkill(String),
#[error("skill '{0}' is already registered: unregister first to replace it")]
AlreadyRegistered(String),
}
fn list(names: &[CapabilityName]) -> String {
names
.iter()
.map(CapabilityName::as_str)
.collect::<Vec<_>>()
.join(", ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capability_refusals_name_the_capabilities() {
let cap = |raw: &str| raw.parse::<CapabilityName>().unwrap();
let over = Error::OverPrivileged {
name: String::from("notes"),
excess: vec![cap("fs.write"), cap("net.egress")],
};
assert_eq!(
over.to_string(),
"skill 'notes' refused: manifest declares capabilities beyond user grants: \
fs.write, net.egress"
);
let undeclared = Error::UndeclaredCapability {
name: String::from("notes"),
capability: cap("fs.read"),
};
assert_eq!(
undeclared.to_string(),
"skill 'notes' denied: capability 'fs.read' was not declared in the manifest"
);
}
}