Skip to content

Commit ba4f808

Browse files
Merge branch 'main' into feat/csharp-sdk-parity
2 parents 75afcdb + 08bf010 commit ba4f808

40 files changed

+630
-36
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@ jobs:
9797
import-module: a2a_agentmesh
9898
- package: crewai-agentmesh
9999
import-module: crewai_agentmesh
100-
- package: dify-plugin
101-
import-module: provider
102100
- package: flowise-agentmesh
103101
import-module: flowise_agentmesh
104102
- package: haystack-agentmesh

packages/agent-mesh/sdks/rust/agentmesh/src/audit.rs

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,30 @@ use std::time::{SystemTime, UNIX_EPOCH};
1414
/// creating a tamper-evident chain from the genesis entry.
1515
pub struct AuditLogger {
1616
entries: Mutex<Vec<AuditEntry>>,
17+
max_entries: Option<usize>,
1718
}
1819

1920
impl AuditLogger {
20-
/// Create an empty audit logger.
21+
/// Create an empty audit logger with no entry limit.
2122
pub fn new() -> Self {
2223
Self {
2324
entries: Mutex::new(Vec::new()),
25+
max_entries: None,
26+
}
27+
}
28+
29+
/// Create an audit logger that retains at most `max` entries,
30+
/// evicting the oldest when the limit is exceeded.
31+
pub fn with_max_entries(max: usize) -> Self {
32+
Self {
33+
entries: Mutex::new(Vec::new()),
34+
max_entries: Some(max),
2435
}
2536
}
2637

2738
/// Append a new entry to the audit chain and return it.
2839
pub fn log(&self, agent_id: &str, action: &str, decision: &str) -> AuditEntry {
29-
let mut entries = self.entries.lock().unwrap();
40+
let mut entries = self.entries.lock().unwrap_or_else(|e| e.into_inner());
3041
let seq = entries.len() as u64;
3142
let prev_hash = entries
3243
.last()
@@ -51,6 +62,15 @@ impl AuditLogger {
5162
};
5263

5364
entries.push(entry.clone());
65+
66+
// Evict oldest entries when the retention limit is exceeded.
67+
if let Some(max) = self.max_entries {
68+
if entries.len() > max {
69+
let overflow = entries.len() - max;
70+
entries.drain(..overflow);
71+
}
72+
}
73+
5474
entry
5575
}
5676

@@ -59,7 +79,7 @@ impl AuditLogger {
5979
/// Returns `true` if every entry's hash is correct and linked to the
6080
/// previous entry's hash.
6181
pub fn verify(&self) -> bool {
62-
let entries = self.entries.lock().unwrap();
82+
let entries = self.entries.lock().unwrap_or_else(|e| e.into_inner());
6383
for (i, entry) in entries.iter().enumerate() {
6484
let expected_prev = if i == 0 {
6585
String::new()
@@ -87,14 +107,23 @@ impl AuditLogger {
87107

88108
/// Return all audit entries.
89109
pub fn entries(&self) -> Vec<AuditEntry> {
90-
self.entries.lock().unwrap().clone()
110+
self.entries
111+
.lock()
112+
.unwrap_or_else(|e| e.into_inner())
113+
.clone()
114+
}
115+
116+
/// Serialise all audit entries to a JSON string.
117+
pub fn export_json(&self) -> String {
118+
let entries = self.entries.lock().unwrap_or_else(|e| e.into_inner());
119+
serde_json::to_string(&*entries).unwrap_or_else(|_| "[]".to_string())
91120
}
92121

93122
/// Return entries matching the given filter.
94123
pub fn get_entries(&self, filter: &AuditFilter) -> Vec<AuditEntry> {
95124
self.entries
96125
.lock()
97-
.unwrap()
126+
.unwrap_or_else(|e| e.into_inner())
98127
.iter()
99128
.filter(|e| {
100129
if let Some(ref id) = filter.agent_id {
@@ -240,4 +269,54 @@ mod tests {
240269
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
241270
);
242271
}
272+
273+
#[test]
274+
fn test_export_json() {
275+
let logger = AuditLogger::new();
276+
logger.log("agent-1", "data.read", "allow");
277+
logger.log("agent-2", "shell:rm", "deny");
278+
let json = logger.export_json();
279+
let parsed: Vec<AuditEntry> = serde_json::from_str(&json).unwrap();
280+
assert_eq!(parsed.len(), 2);
281+
assert_eq!(parsed[0].agent_id, "agent-1");
282+
assert_eq!(parsed[1].agent_id, "agent-2");
283+
}
284+
285+
#[test]
286+
fn test_export_json_empty() {
287+
let logger = AuditLogger::new();
288+
let json = logger.export_json();
289+
assert_eq!(json, "[]");
290+
}
291+
292+
#[test]
293+
fn test_max_entries_eviction() {
294+
let logger = AuditLogger::with_max_entries(3);
295+
for i in 0..5 {
296+
logger.log("agent", &format!("action-{}", i), "allow");
297+
}
298+
let entries = logger.entries();
299+
assert_eq!(entries.len(), 3);
300+
// Oldest entries (action-0, action-1) should have been evicted
301+
assert_eq!(entries[0].action, "action-2");
302+
assert_eq!(entries[1].action, "action-3");
303+
assert_eq!(entries[2].action, "action-4");
304+
}
305+
306+
#[test]
307+
fn test_max_entries_not_exceeded() {
308+
let logger = AuditLogger::with_max_entries(10);
309+
logger.log("a", "x", "allow");
310+
logger.log("b", "y", "deny");
311+
assert_eq!(logger.entries().len(), 2);
312+
}
313+
314+
#[test]
315+
fn test_no_limit_grows_unbounded() {
316+
let logger = AuditLogger::new();
317+
for i in 0..100 {
318+
logger.log("a", &format!("act-{}", i), "allow");
319+
}
320+
assert_eq!(logger.entries().len(), 100);
321+
}
243322
}

packages/agent-mesh/sdks/rust/agentmesh/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ pub use audit::AuditLogger;
2828
pub use identity::{AgentIdentity, PublicIdentity};
2929
pub use policy::{PolicyEngine, PolicyError};
3030
pub use trust::{TrustConfig, TrustManager};
31-
pub use types::{AuditEntry, AuditFilter, GovernanceResult, PolicyDecision, TrustScore, TrustTier};
31+
pub use types::{
32+
AuditEntry, AuditFilter, CandidateDecision, ConflictResolutionStrategy, GovernanceResult,
33+
PolicyDecision, PolicyScope, ResolutionResult, TrustScore, TrustTier,
34+
};
3235

3336
use std::collections::HashMap;
3437

0 commit comments

Comments
 (0)