aidens_capability_kit/
lib.rs1use aidens_contracts::{
4 CapabilityGateDecisionV1, CapabilityGateOutcomeV1, CapabilityStateV1, RuntimeCapabilityTruthV1,
5 ToolLifecycleStateV1,
6};
7
8pub fn truth(
9 capability_id: impl Into<String>,
10 states: Vec<CapabilityStateV1>,
11 reason: Option<String>,
12) -> RuntimeCapabilityTruthV1 {
13 RuntimeCapabilityTruthV1 {
14 capability_id: capability_id.into(),
15 states,
16 reason,
17 }
18}
19
20pub fn disabled(
21 capability_id: impl Into<String>,
22 reason: impl Into<String>,
23) -> RuntimeCapabilityTruthV1 {
24 RuntimeCapabilityTruthV1 {
25 capability_id: capability_id.into(),
26 states: vec![CapabilityStateV1::Disabled],
27 reason: Some(reason.into()),
28 }
29}
30
31pub fn healthy(capability_id: impl Into<String>) -> RuntimeCapabilityTruthV1 {
32 RuntimeCapabilityTruthV1 {
33 capability_id: capability_id.into(),
34 states: vec![
35 CapabilityStateV1::Configured,
36 CapabilityStateV1::Available,
37 CapabilityStateV1::Healthy,
38 ],
39 reason: None,
40 }
41}
42
43pub fn executable_this_turn(capability: &RuntimeCapabilityTruthV1) -> bool {
44 capability
45 .states
46 .contains(&CapabilityStateV1::ExecutableThisTurn)
47 && !capability.states.contains(&CapabilityStateV1::Disabled)
48 && !capability
49 .states
50 .contains(&CapabilityStateV1::BlockedByPolicy)
51}
52
53pub fn gate_decision(
54 capability_id: impl Into<String>,
55 outcome: CapabilityGateOutcomeV1,
56 lifecycle: Vec<ToolLifecycleStateV1>,
57 executable_this_turn: bool,
58 reason_codes: Vec<String>,
59) -> CapabilityGateDecisionV1 {
60 CapabilityGateDecisionV1::new(
61 capability_id,
62 outcome,
63 lifecycle,
64 executable_this_turn,
65 reason_codes,
66 )
67}
68
69pub fn gate_exposes(decision: &CapabilityGateDecisionV1) -> bool {
70 decision.outcome == CapabilityGateOutcomeV1::Exposed
71 && (decision
72 .lifecycle
73 .contains(&ToolLifecycleStateV1::ExposedThisTurn)
74 || decision.lifecycle.contains(&ToolLifecycleStateV1::Exposed))
75 && decision.executable_this_turn
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn disabled_is_not_healthy() {
84 let state = disabled("web", "local-only mode");
85 assert!(state.states.contains(&CapabilityStateV1::Disabled));
86 assert!(!state.states.contains(&CapabilityStateV1::Healthy));
87 assert!(!executable_this_turn(&state));
88 }
89
90 #[test]
91 fn can_represent_registered_exposed_executable_degraded_and_blocked() {
92 let state = truth(
93 "tool:test:1",
94 vec![
95 CapabilityStateV1::Configured,
96 CapabilityStateV1::Available,
97 CapabilityStateV1::Registered,
98 CapabilityStateV1::ExposedThisTurn,
99 CapabilityStateV1::ExecutableThisTurn,
100 CapabilityStateV1::Degraded,
101 ],
102 Some("parser fallback active".into()),
103 );
104 assert!(executable_this_turn(&state));
105
106 let blocked = truth(
107 "tool:test:1",
108 vec![
109 CapabilityStateV1::Registered,
110 CapabilityStateV1::ExecutableThisTurn,
111 CapabilityStateV1::BlockedByPolicy,
112 ],
113 Some("missing permit".into()),
114 );
115 assert!(!executable_this_turn(&blocked));
116 }
117
118 #[test]
119 fn gate_decision_requires_exposed_lifecycle_to_expose() {
120 let decision = gate_decision(
121 "tool:test:1",
122 CapabilityGateOutcomeV1::Exposed,
123 vec![
124 ToolLifecycleStateV1::Declared,
125 ToolLifecycleStateV1::Registered,
126 ToolLifecycleStateV1::Executable,
127 ToolLifecycleStateV1::ExposedThisTurn,
128 ],
129 true,
130 Vec::new(),
131 );
132
133 assert!(gate_exposes(&decision));
134 }
135}