Skip to main content

aidens_capability_kit/
lib.rs

1//! Runtime capability truth surfaces.
2
3use 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}