Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/ironclaw_engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ pub mod workspace;
// ── Re-exports: types ───────────────────────────────────────

pub use types::capability::{
ActionDef, Capability, CapabilityLease, EffectType, GrantedActions, LeaseId, PolicyCondition,
PolicyEffect, PolicyRule,
ActionDef, Capability, CapabilityLease, CapabilityStatus, EffectType, GrantedActions, LeaseId,
PolicyCondition, PolicyEffect, PolicyRule,
};
pub use types::error::{CapabilityError, EngineError, StepError, ThreadError};
pub use types::event::{EventId, EventKind, ThreadEvent};
Expand Down
69 changes: 69 additions & 0 deletions crates/ironclaw_engine/src/types/capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,31 @@ pub struct ActionDef {
pub requires_approval: bool,
}

/// Canonical model-visible status for capability background surfacing.
///
/// This is a normalized projection over host runtime truth. It is not itself
/// a source of truth for auth, activation, or installation state.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CapabilityStatus {
/// Capability is directly usable now.
Ready,
/// Capability is usable now, but only through a scoped or indirect route.
ReadyScoped,
/// Capability exists but needs authentication before use.
NeedsAuth,
/// Capability exists but needs setup before auth or execution can proceed.
NeedsSetup,
/// Capability is installed or known, but not currently active.
Inactive,
/// Capability is known to the runtime but not yet activated into a direct action.
Latent,
/// Capability lookup or readiness failed with a concrete runtime error.
Error,
/// Capability is known in the registry but not installed.
AvailableNotInstalled,
}

// ── Capability ──────────────────────────────────────────────

/// A capability — bundles actions, knowledge, and policies.
Expand Down Expand Up @@ -257,6 +282,7 @@ impl CapabilityLease {
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

fn make_lease() -> CapabilityLease {
CapabilityLease {
Expand Down Expand Up @@ -359,4 +385,47 @@ mod tests {
lease.granted_actions = GrantedActions::Specific(vec!["list-prs".into()]);
assert!(lease.covers_action("list_prs"));
}

#[test]
fn capability_status_serializes_as_snake_case() {
let cases = [
(CapabilityStatus::Ready, json!("ready")),
(CapabilityStatus::ReadyScoped, json!("ready_scoped")),
(CapabilityStatus::NeedsAuth, json!("needs_auth")),
(CapabilityStatus::NeedsSetup, json!("needs_setup")),
(CapabilityStatus::Inactive, json!("inactive")),
(CapabilityStatus::Latent, json!("latent")),
(CapabilityStatus::Error, json!("error")),
(
CapabilityStatus::AvailableNotInstalled,
json!("available_not_installed"),
),
];

for (status, expected) in cases {
assert_eq!(serde_json::to_value(status).unwrap(), expected);
}
}

#[test]
fn capability_status_round_trips_from_wire_values() {
let cases = [
("ready", CapabilityStatus::Ready),
("ready_scoped", CapabilityStatus::ReadyScoped),
("needs_auth", CapabilityStatus::NeedsAuth),
("needs_setup", CapabilityStatus::NeedsSetup),
("inactive", CapabilityStatus::Inactive),
("latent", CapabilityStatus::Latent),
("error", CapabilityStatus::Error),
(
"available_not_installed",
CapabilityStatus::AvailableNotInstalled,
),
];

for (wire, expected) in cases {
let parsed: CapabilityStatus = serde_json::from_value(json!(wire)).unwrap();
assert_eq!(parsed, expected);
}
}
}
Loading