1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ToolCall {
13 pub id: String,
15
16 pub session_id: String,
18
19 pub timestamp: DateTime<Utc>,
21
22 pub tool_name: String,
24
25 pub input: Value,
27
28 pub duration_ms: Option<u64>,
30
31 pub output: Option<String>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub enum FileOperation {
38 Read,
39 Write,
40 Edit,
41 Glob,
42 Grep,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct FileAccess {
48 pub session_id: String,
49 pub timestamp: DateTime<Utc>,
50 pub path: String,
51 pub operation: FileOperation,
52 pub line_range: Option<(u64, u64)>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct BashCommand {
59 pub session_id: String,
60 pub timestamp: DateTime<Utc>,
61 pub command: String,
62 pub is_destructive: bool,
63 pub output_preview: String,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub enum NetworkTool {
70 WebFetch,
71 WebSearch,
72 McpCall { server: String },
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct NetworkCall {
78 pub session_id: String,
79 pub timestamp: DateTime<Utc>,
80 pub url: String,
82 pub tool: NetworkTool,
83 pub domain: String,
85}
86
87#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
89pub enum AlertSeverity {
90 Info,
91 Warning,
92 Critical,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub enum AlertCategory {
98 CredentialAccess,
99 DestructiveCommand,
100 ExternalExfil,
101 ScopeViolation,
102 ForcePush,
103}
104
105impl AlertCategory {
106 pub fn action_hint(&self) -> &'static str {
109 match self {
110 AlertCategory::CredentialAccess => {
111 "Verify the credential wasn't exposed. If it was, rotate it immediately."
112 }
113 AlertCategory::DestructiveCommand => {
114 "Check if deleted files are recoverable (Trash, git stash, backup)."
115 }
116 AlertCategory::ExternalExfil => {
117 "Review what data was sent to this domain and whether it was intentional."
118 }
119 AlertCategory::ScopeViolation => {
120 "Inspect the file written outside the project root. Delete it if unintended."
121 }
122 AlertCategory::ForcePush => {
123 "Run git reflog to find the overwritten commit. Force-push a revert if needed."
124 }
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_action_hint_all_variants_non_empty() {
135 let variants = [
138 AlertCategory::CredentialAccess,
139 AlertCategory::DestructiveCommand,
140 AlertCategory::ExternalExfil,
141 AlertCategory::ScopeViolation,
142 AlertCategory::ForcePush,
143 ];
144 for variant in &variants {
145 let hint = variant.action_hint();
146 assert!(!hint.is_empty(), "{:?} has an empty action hint", variant);
147 }
148 }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct Alert {
154 pub session_id: String,
155 pub timestamp: DateTime<Utc>,
156 pub severity: AlertSeverity,
157 pub category: AlertCategory,
158 pub detail: String,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, Default)]
163pub struct ActivitySummary {
164 pub file_accesses: Vec<FileAccess>,
165 pub bash_commands: Vec<BashCommand>,
166 pub network_calls: Vec<NetworkCall>,
167 pub alerts: Vec<Alert>,
168}