Skip to main content

mentedb_core/
space.rs

1//! Memory Spaces: isolated, permission-controlled namespaces for memories.
2
3use std::collections::HashMap;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use serde::{Deserialize, Serialize};
7
8use crate::types::{AgentId, SpaceId, Timestamp};
9
10/// Access permission level.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum Permission {
13    Read,
14    Write,
15    ReadWrite,
16    Admin,
17}
18
19impl Permission {
20    /// Returns `true` when `self` satisfies `required`.
21    fn satisfies(self, required: Permission) -> bool {
22        matches!(
23            (self, required),
24            (Permission::Admin, _)
25                | (Permission::ReadWrite, Permission::Read)
26                | (Permission::ReadWrite, Permission::Write)
27                | (Permission::ReadWrite, Permission::ReadWrite)
28                | (Permission::Read, Permission::Read)
29                | (Permission::Write, Permission::Write)
30        )
31    }
32}
33
34/// One entry in a space's access-control list.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct AccessEntry {
37    /// The agent granted access.
38    pub agent_id: AgentId,
39    /// The permission level granted.
40    pub permission: Permission,
41}
42
43/// A namespace that groups memories and controls access.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct MemorySpace {
46    /// Unique identifier for this space.
47    pub id: SpaceId,
48    /// Human readable name.
49    pub name: String,
50    /// The agent that owns this space.
51    pub owner: AgentId,
52    /// Access control list for other agents.
53    pub access_list: Vec<AccessEntry>,
54    /// When this space was created.
55    pub created_at: Timestamp,
56    /// Optional capacity limit on stored memories.
57    pub max_memories: Option<usize>,
58    /// Current number of memories in this space.
59    pub current_count: usize,
60}
61
62/// Manages the set of memory spaces.
63#[derive(Debug, Default)]
64pub struct SpaceManager {
65    spaces: HashMap<SpaceId, MemorySpace>,
66}
67
68fn now_micros() -> Timestamp {
69    SystemTime::now()
70        .duration_since(UNIX_EPOCH)
71        .unwrap_or_default()
72        .as_micros() as Timestamp
73}
74
75impl SpaceManager {
76    /// Creates a new empty space manager.
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    /// Create a new space owned by `owner`.
82    pub fn create_space(&mut self, name: &str, owner: AgentId) -> MemorySpace {
83        let space = MemorySpace {
84            id: SpaceId::new(),
85            name: name.to_string(),
86            owner,
87            access_list: vec![AccessEntry {
88                agent_id: owner,
89                permission: Permission::Admin,
90            }],
91            created_at: now_micros(),
92            max_memories: None,
93            current_count: 0,
94        };
95        self.spaces.insert(space.id, space.clone());
96        space
97    }
98
99    /// Returns a reference to the space with the given ID, if it exists.
100    pub fn get_space(&self, id: SpaceId) -> Option<&MemorySpace> {
101        self.spaces.get(&id)
102    }
103
104    /// Removes a space by ID.
105    pub fn delete_space(&mut self, id: SpaceId) {
106        self.spaces.remove(&id);
107    }
108
109    /// Grant `perm` to `agent` in the given space.
110    pub fn grant_access(&mut self, space: SpaceId, agent: AgentId, perm: Permission) {
111        if let Some(s) = self.spaces.get_mut(&space) {
112            // Replace existing entry for this agent, or append.
113            if let Some(entry) = s.access_list.iter_mut().find(|e| e.agent_id == agent) {
114                entry.permission = perm;
115            } else {
116                s.access_list.push(AccessEntry {
117                    agent_id: agent,
118                    permission: perm,
119                });
120            }
121        }
122    }
123
124    /// Remove all access for `agent` in the given space.
125    pub fn revoke_access(&mut self, space: SpaceId, agent: AgentId) {
126        if let Some(s) = self.spaces.get_mut(&space) {
127            s.access_list.retain(|e| e.agent_id != agent);
128        }
129    }
130
131    /// Check whether `agent` has at least `required` permission in the space.
132    pub fn check_access(&self, space: SpaceId, agent: AgentId, required: Permission) -> bool {
133        self.spaces.get(&space).is_some_and(|s| {
134            if s.owner == agent {
135                return true;
136            }
137            s.access_list
138                .iter()
139                .any(|e| e.agent_id == agent && e.permission.satisfies(required))
140        })
141    }
142
143    /// List all spaces that `agent` owns or has an ACL entry in.
144    pub fn list_spaces_for_agent(&self, agent: AgentId) -> Vec<&MemorySpace> {
145        self.spaces
146            .values()
147            .filter(|s| s.owner == agent || s.access_list.iter().any(|e| e.agent_id == agent))
148            .collect()
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    fn agent() -> AgentId {
157        AgentId::new()
158    }
159
160    #[test]
161    fn create_and_get() {
162        let mut mgr = SpaceManager::new();
163        let owner = agent();
164        let sp = mgr.create_space("test", owner);
165        assert_eq!(mgr.get_space(sp.id).unwrap().name, "test");
166    }
167
168    #[test]
169    fn owner_has_admin() {
170        let mut mgr = SpaceManager::new();
171        let owner = agent();
172        let sp = mgr.create_space("s", owner);
173        assert!(mgr.check_access(sp.id, owner, Permission::Admin));
174    }
175
176    #[test]
177    fn grant_and_check() {
178        let mut mgr = SpaceManager::new();
179        let owner = agent();
180        let reader = agent();
181        let sp = mgr.create_space("s", owner);
182        mgr.grant_access(sp.id, reader, Permission::Read);
183        assert!(mgr.check_access(sp.id, reader, Permission::Read));
184        assert!(!mgr.check_access(sp.id, reader, Permission::Write));
185    }
186
187    #[test]
188    fn revoke_access() {
189        let mut mgr = SpaceManager::new();
190        let owner = agent();
191        let a = agent();
192        let sp = mgr.create_space("s", owner);
193        mgr.grant_access(sp.id, a, Permission::ReadWrite);
194        mgr.revoke_access(sp.id, a);
195        assert!(!mgr.check_access(sp.id, a, Permission::Read));
196    }
197
198    #[test]
199    fn list_spaces_for_agent() {
200        let mut mgr = SpaceManager::new();
201        let o1 = agent();
202        let o2 = agent();
203        mgr.create_space("s1", o1);
204        mgr.create_space("s2", o2);
205        assert_eq!(mgr.list_spaces_for_agent(o1).len(), 1);
206    }
207}