use sha2::Digest;
pub const CAPABILITY_HASH_DOMAIN_V2: &[u8] = b"codewhale-plugin-capabilities-v2\0";
pub const CAPABILITY_HASH_DOMAIN_V1: &[u8] = b"codewhale-plugin-capabilities-v1\0";
pub const ACTIVATION_POLICY_VERSION: u32 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PluginActivationCapability {
Skills,
McpStdio,
McpRemote,
Commands,
Agents,
Hooks,
Lsp,
Native,
FilesystemRoots,
LifecycleMutation,
}
impl PluginActivationCapability {
pub const ALL: &'static [Self] = &[
Self::Skills,
Self::McpStdio,
Self::McpRemote,
Self::Commands,
Self::Agents,
Self::Hooks,
Self::Lsp,
Self::Native,
Self::FilesystemRoots,
Self::LifecycleMutation,
];
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Skills => "skills",
Self::McpStdio => "mcp-stdio",
Self::McpRemote => "mcp-remote",
Self::Commands => "commands",
Self::Agents => "agents",
Self::Hooks => "hooks",
Self::Lsp => "lsp",
Self::Native => "native",
Self::FilesystemRoots => "filesystem-roots",
Self::LifecycleMutation => "lifecycle-mutation",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PluginActivationPolicy {
pub version: u32,
pub supported: &'static [PluginActivationCapability],
pub inactive: &'static [PluginActivationCapability],
}
impl PluginActivationPolicy {
#[must_use]
pub const fn current() -> Self {
Self {
version: ACTIVATION_POLICY_VERSION,
supported: &[
PluginActivationCapability::Skills,
PluginActivationCapability::McpStdio,
PluginActivationCapability::McpRemote,
],
inactive: &[
PluginActivationCapability::Commands,
PluginActivationCapability::Agents,
PluginActivationCapability::Hooks,
PluginActivationCapability::Lsp,
PluginActivationCapability::Native,
PluginActivationCapability::FilesystemRoots,
PluginActivationCapability::LifecycleMutation,
],
}
}
#[must_use]
pub fn is_supported(self, capability: PluginActivationCapability) -> bool {
self.supported.contains(&capability)
}
pub fn write_hash_material(self, hasher: &mut impl Digest) {
hasher.update(CAPABILITY_HASH_DOMAIN_V2);
hasher.update(b"policy-version\0");
hasher.update(self.version.to_string().as_bytes());
hasher.update(b"\0");
for capability in self.supported {
hasher.update(b"supported\0");
hasher.update(capability.as_str().as_bytes());
hasher.update(b"\0");
}
for capability in self.inactive {
hasher.update(b"inactive\0");
hasher.update(capability.as_str().as_bytes());
hasher.update(b"\0");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_activation_policy_partitions_known_capabilities() {
let policy = PluginActivationPolicy::current();
for capability in PluginActivationCapability::ALL {
let supported = policy.supported.contains(capability);
let inactive = policy.inactive.contains(capability);
assert_ne!(
supported, inactive,
"{capability:?} must be supported or inactive, not both or neither"
);
}
}
}