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