Skip to main content

mnemo_core/query/
share.rs

1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3
4use crate::error::{Error, Result};
5use crate::model::acl::{Acl, Permission, PrincipalType};
6use crate::model::event::EventType;
7use crate::model::memory::Scope;
8use crate::query::MnemoEngine;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ShareRequest {
12    pub memory_id: Uuid,
13    pub agent_id: Option<String>,
14    pub target_agent_id: String,
15    pub target_agent_ids: Option<Vec<String>>,
16    pub permission: Option<Permission>,
17    pub expires_in_hours: Option<f64>,
18}
19
20impl ShareRequest {
21    pub fn new(memory_id: Uuid, target_agent_id: String) -> Self {
22        Self {
23            memory_id,
24            agent_id: None,
25            target_agent_id,
26            target_agent_ids: None,
27            permission: None,
28            expires_in_hours: None,
29        }
30    }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ShareResponse {
35    pub acl_id: Uuid,
36    pub acl_ids: Vec<Uuid>,
37    pub memory_id: Uuid,
38    pub shared_with: String,
39    pub shared_with_all: Vec<String>,
40    pub permission: Permission,
41}
42
43pub async fn execute(engine: &MnemoEngine, request: ShareRequest) -> Result<ShareResponse> {
44    let agent_id = request
45        .agent_id
46        .unwrap_or_else(|| engine.default_agent_id.clone());
47    let permission = request.permission.unwrap_or(Permission::Read);
48
49    // Verify the requester owns or has admin access to the memory
50    let has_access = engine
51        .storage
52        .check_permission(request.memory_id, &agent_id, Permission::Admin)
53        .await?;
54
55    if !has_access {
56        return Err(Error::PermissionDenied(format!(
57            "agent {agent_id} cannot share memory {}",
58            request.memory_id
59        )));
60    }
61
62    // Build list of targets: multi-target takes precedence over single target
63    let targets = if let Some(ref ids) = request.target_agent_ids {
64        ids.clone()
65    } else {
66        vec![request.target_agent_id.clone()]
67    };
68
69    // Compute expiration from expires_in_hours
70    let expires_at = request.expires_in_hours.map(|h| {
71        let exp = chrono::Utc::now() + chrono::Duration::seconds((h * 3600.0) as i64);
72        exp.to_rfc3339()
73    });
74
75    let now = chrono::Utc::now().to_rfc3339();
76    let mut acl_ids = Vec::new();
77
78    for target in &targets {
79        let acl_id = Uuid::now_v7();
80        let acl = Acl {
81            id: acl_id,
82            memory_id: request.memory_id,
83            principal_type: PrincipalType::Agent,
84            principal_id: target.clone(),
85            permission,
86            granted_by: agent_id.clone(),
87            created_at: now.clone(),
88            expires_at: expires_at.clone(),
89        };
90        engine.storage.insert_acl(&acl).await?;
91        acl_ids.push(acl_id);
92    }
93
94    // Optionally update scope to Shared if it was Private
95    if let Some(mut record) = engine.storage.get_memory(request.memory_id).await?
96        && record.scope == Scope::Private
97    {
98        record.scope = Scope::Shared;
99        record.updated_at = now.clone();
100        engine.storage.update_memory(&record).await?;
101    }
102
103    // Emit MemoryShare event (fire-and-forget)
104    let mut event = super::event_builder::build_event(
105        engine,
106        &agent_id,
107        EventType::MemoryShare,
108        serde_json::json!({
109            "memory_id": request.memory_id.to_string(),
110            "shared_with": targets,
111            "permission": permission.to_string(),
112        }),
113        &request.memory_id.to_string(),
114        None,
115    )
116    .await;
117    if engine.embed_events
118        && let Ok(emb) = engine.embedding.embed(&event.payload.to_string()).await
119    {
120        event.embedding = Some(emb);
121    }
122    if let Err(e) = engine.storage.insert_event(&event).await {
123        tracing::error!(event_id = %event.id, error = %e, "failed to insert audit event");
124    }
125
126    let first_acl_id = acl_ids[0];
127    let first_target = targets[0].clone();
128
129    Ok(ShareResponse {
130        acl_id: first_acl_id,
131        acl_ids,
132        memory_id: request.memory_id,
133        shared_with: first_target,
134        shared_with_all: targets,
135        permission,
136    })
137}