use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::error::{Error, Result};
use crate::model::acl::{Acl, Permission, PrincipalType};
use crate::model::event::EventType;
use crate::model::memory::Scope;
use crate::query::MnemoEngine;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShareRequest {
pub memory_id: Uuid,
pub agent_id: Option<String>,
pub target_agent_id: String,
pub target_agent_ids: Option<Vec<String>>,
pub permission: Option<Permission>,
pub expires_in_hours: Option<f64>,
}
impl ShareRequest {
pub fn new(memory_id: Uuid, target_agent_id: String) -> Self {
Self {
memory_id,
agent_id: None,
target_agent_id,
target_agent_ids: None,
permission: None,
expires_in_hours: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShareResponse {
pub acl_id: Uuid,
pub acl_ids: Vec<Uuid>,
pub memory_id: Uuid,
pub shared_with: String,
pub shared_with_all: Vec<String>,
pub permission: Permission,
}
pub async fn execute(engine: &MnemoEngine, request: ShareRequest) -> Result<ShareResponse> {
let agent_id = request
.agent_id
.unwrap_or_else(|| engine.default_agent_id.clone());
let permission = request.permission.unwrap_or(Permission::Read);
let has_access = engine
.storage
.check_permission(request.memory_id, &agent_id, Permission::Admin)
.await?;
if !has_access {
return Err(Error::PermissionDenied(format!(
"agent {agent_id} cannot share memory {}",
request.memory_id
)));
}
let targets = if let Some(ref ids) = request.target_agent_ids {
ids.clone()
} else {
vec![request.target_agent_id.clone()]
};
let expires_at = request.expires_in_hours.map(|h| {
let exp = chrono::Utc::now() + chrono::Duration::seconds((h * 3600.0) as i64);
exp.to_rfc3339()
});
let now = chrono::Utc::now().to_rfc3339();
let mut acl_ids = Vec::new();
for target in &targets {
let acl_id = Uuid::now_v7();
let acl = Acl {
id: acl_id,
memory_id: request.memory_id,
principal_type: PrincipalType::Agent,
principal_id: target.clone(),
permission,
granted_by: agent_id.clone(),
created_at: now.clone(),
expires_at: expires_at.clone(),
};
engine.storage.insert_acl(&acl).await?;
acl_ids.push(acl_id);
}
if let Some(mut record) = engine.storage.get_memory(request.memory_id).await?
&& record.scope == Scope::Private
{
record.scope = Scope::Shared;
record.updated_at = now.clone();
engine.storage.update_memory(&record).await?;
}
let mut event = super::event_builder::build_event(
engine,
&agent_id,
EventType::MemoryShare,
serde_json::json!({
"memory_id": request.memory_id.to_string(),
"shared_with": targets,
"permission": permission.to_string(),
}),
&request.memory_id.to_string(),
None,
)
.await;
if engine.embed_events
&& let Ok(emb) = engine.embedding.embed(&event.payload.to_string()).await
{
event.embedding = Some(emb);
}
if let Err(e) = engine.storage.insert_event(&event).await {
tracing::error!(event_id = %event.id, error = %e, "failed to insert audit event");
}
let first_acl_id = acl_ids[0];
let first_target = targets[0].clone();
Ok(ShareResponse {
acl_id: first_acl_id,
acl_ids,
memory_id: request.memory_id,
shared_with: first_target,
shared_with_all: targets,
permission,
})
}