Skip to main content

qae_kernel/
action.rs

1// SPDX-License-Identifier: BUSL-1.1
2//! Proposed action types for the safety certification kernel.
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7/// Priority level of a proposed action.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum ActionPriority {
10    Critical,
11    High,
12    Standard,
13    Low,
14}
15
16/// A single dimension of state change proposed by an action.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct StateDelta {
19    /// Which state dimension is changing (e.g., "AAPL_weight").
20    pub dimension: String,
21    /// Current value in that dimension.
22    pub from_value: f64,
23    /// Proposed new value.
24    pub to_value: f64,
25}
26
27/// Trait representing an action proposed by an agent for safety certification.
28///
29/// Object-safe: can be used as `dyn ProposedAction`.
30pub trait ProposedAction: Send + Sync {
31    /// Unique identifier for this action.
32    fn action_id(&self) -> &str;
33
34    /// Identifier of the agent proposing the action.
35    fn agent_id(&self) -> &str;
36
37    /// When the action was proposed.
38    fn proposed_at(&self) -> DateTime<Utc>;
39
40    /// State deltas this action would cause.
41    fn state_deltas(&self) -> &[StateDelta];
42
43    /// Priority level.
44    fn priority(&self) -> ActionPriority;
45}
46
47/// Simple concrete implementation of `ProposedAction` for testing and basic use.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct SimpleAction {
50    pub action_id: String,
51    pub agent_id: String,
52    pub proposed_at: DateTime<Utc>,
53    pub state_deltas: Vec<StateDelta>,
54    pub priority: ActionPriority,
55}
56
57impl ProposedAction for SimpleAction {
58    fn action_id(&self) -> &str {
59        &self.action_id
60    }
61
62    fn agent_id(&self) -> &str {
63        &self.agent_id
64    }
65
66    fn proposed_at(&self) -> DateTime<Utc> {
67        self.proposed_at
68    }
69
70    fn state_deltas(&self) -> &[StateDelta] {
71        &self.state_deltas
72    }
73
74    fn priority(&self) -> ActionPriority {
75        self.priority
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn simple_action_implements_trait() {
85        let action = SimpleAction {
86            action_id: "act-1".into(),
87            agent_id: "agent-1".into(),
88            proposed_at: Utc::now(),
89            state_deltas: vec![StateDelta {
90                dimension: "weight_AAPL".into(),
91                from_value: 0.10,
92                to_value: 0.15,
93            }],
94            priority: ActionPriority::Standard,
95        };
96
97        let dyn_action: &dyn ProposedAction = &action;
98        assert_eq!(dyn_action.action_id(), "act-1");
99        assert_eq!(dyn_action.agent_id(), "agent-1");
100        assert_eq!(dyn_action.state_deltas().len(), 1);
101        assert_eq!(dyn_action.priority(), ActionPriority::Standard);
102    }
103
104    #[test]
105    fn action_priority_serialization() {
106        let json = serde_json::to_string(&ActionPriority::Critical).unwrap();
107        let deserialized: ActionPriority = serde_json::from_str(&json).unwrap();
108        assert_eq!(deserialized, ActionPriority::Critical);
109    }
110
111    #[test]
112    fn state_delta_serialization() {
113        let delta = StateDelta {
114            dimension: "x".into(),
115            from_value: 1.0,
116            to_value: 2.0,
117        };
118        let json = serde_json::to_string(&delta).unwrap();
119        let deserialized: StateDelta = serde_json::from_str(&json).unwrap();
120        assert_eq!(deserialized.dimension, "x");
121        assert!((deserialized.from_value - 1.0).abs() < f64::EPSILON);
122        assert!((deserialized.to_value - 2.0).abs() < f64::EPSILON);
123    }
124}