tap_node/event/
decision_log_handler.rs1use crate::state_machine::fsm::{Decision, DecisionHandler, TransactionContext};
9use crate::storage::{DecisionType, Storage};
10use async_trait::async_trait;
11use serde_json::json;
12use std::sync::Arc;
13use tracing::{debug, error};
14
15#[derive(Debug)]
17pub struct DecisionLogHandler {
18 storage: Arc<Storage>,
19 agent_dids: Vec<String>,
20}
21
22impl DecisionLogHandler {
23 pub fn new(storage: Arc<Storage>, agent_dids: Vec<String>) -> Self {
25 Self {
26 storage,
27 agent_dids,
28 }
29 }
30}
31
32#[async_trait]
33impl DecisionHandler for DecisionLogHandler {
34 async fn handle_decision(&self, ctx: &TransactionContext, decision: &Decision) {
35 let (decision_type, context_json) = match decision {
36 Decision::AuthorizationRequired {
37 transaction_id,
38 pending_agents,
39 } => (
40 DecisionType::AuthorizationRequired,
41 json!({
42 "transaction_state": ctx.state.to_string(),
43 "pending_agents": pending_agents,
44 "transaction_id": transaction_id,
45 }),
46 ),
47 Decision::PolicySatisfactionRequired {
48 transaction_id,
49 requested_by,
50 } => (
51 DecisionType::PolicySatisfactionRequired,
52 json!({
53 "transaction_state": ctx.state.to_string(),
54 "requested_by": requested_by,
55 "transaction_id": transaction_id,
56 }),
57 ),
58 Decision::SettlementRequired { transaction_id } => (
59 DecisionType::SettlementRequired,
60 json!({
61 "transaction_state": ctx.state.to_string(),
62 "transaction_id": transaction_id,
63 }),
64 ),
65 };
66
67 let agent_did = self.agent_dids.first().cloned().unwrap_or_default();
68
69 match self
70 .storage
71 .insert_decision(
72 &ctx.transaction_id,
73 &agent_did,
74 decision_type,
75 &context_json,
76 )
77 .await
78 {
79 Ok(decision_id) => {
80 debug!(
81 "Logged decision {} for transaction {} (poll mode)",
82 decision_id, ctx.transaction_id
83 );
84 }
85 Err(e) => {
86 error!(
87 "Failed to log decision for transaction {}: {}",
88 ctx.transaction_id, e
89 );
90 }
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::state_machine::fsm::TransactionState;
99 use crate::storage::DecisionStatus;
100
101 #[tokio::test]
102 async fn test_decision_log_handler_writes_to_db() {
103 let storage = Arc::new(Storage::new_in_memory().await.unwrap());
104 let handler =
105 DecisionLogHandler::new(storage.clone(), vec!["did:key:z6MkAgent1".to_string()]);
106
107 let ctx = TransactionContext {
108 transaction_id: "txn-dlh-1".to_string(),
109 state: TransactionState::Received,
110 agents: Default::default(),
111 has_pending_policies: false,
112 };
113
114 let decision = Decision::AuthorizationRequired {
115 transaction_id: "txn-dlh-1".to_string(),
116 pending_agents: vec!["did:key:z6MkAgent1".to_string()],
117 };
118
119 handler.handle_decision(&ctx, &decision).await;
120
121 let entries = storage
122 .list_decisions(
123 Some("did:key:z6MkAgent1"),
124 Some(DecisionStatus::Pending),
125 None,
126 100,
127 )
128 .await
129 .unwrap();
130 assert_eq!(entries.len(), 1);
131 assert_eq!(entries[0].transaction_id, "txn-dlh-1");
132 assert_eq!(
133 entries[0].decision_type,
134 DecisionType::AuthorizationRequired
135 );
136 }
137
138 #[tokio::test]
139 async fn test_decision_log_handler_settlement() {
140 let storage = Arc::new(Storage::new_in_memory().await.unwrap());
141 let handler =
142 DecisionLogHandler::new(storage.clone(), vec!["did:key:z6MkAgent1".to_string()]);
143
144 let ctx = TransactionContext {
145 transaction_id: "txn-dlh-2".to_string(),
146 state: TransactionState::ReadyToSettle,
147 agents: Default::default(),
148 has_pending_policies: false,
149 };
150
151 let decision = Decision::SettlementRequired {
152 transaction_id: "txn-dlh-2".to_string(),
153 };
154
155 handler.handle_decision(&ctx, &decision).await;
156
157 let entries = storage
158 .list_decisions(
159 Some("did:key:z6MkAgent1"),
160 Some(DecisionStatus::Pending),
161 None,
162 100,
163 )
164 .await
165 .unwrap();
166 assert_eq!(entries.len(), 1);
167 assert_eq!(entries[0].decision_type, DecisionType::SettlementRequired);
168 }
169}