Skip to main content

pe_core/
scope.rs

1//! ExecutionScope — multi-tenant isolation.
2//!
3//! Every operation carries a scope. No operation happens without one.
4//! Based on Group 27 of the pre-plan.
5
6use serde::{Deserialize, Serialize};
7
8pub type TenantId = String;
9pub type UserId = String;
10pub type SessionId = String;
11pub type ThreadId = String;
12pub type TaskId = String;
13
14/// Execution scope — flows through every storage operation.
15///
16/// Isolation rules:
17/// - tenant_id:  complete isolation. Tenant A never sees Tenant B.
18/// - user_id:    within tenant, users share collective but not private memory.
19/// - session_id: within user, sessions share long-term memory but not in-progress state.
20/// - thread_id:  within session, threads are independent conversations.
21/// - task_id:    within thread, tasks track specific work items.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ExecutionScope {
24    pub tenant_id: TenantId,
25    pub user_id: UserId,
26    pub session_id: SessionId,
27    pub thread_id: ThreadId,
28    pub task_id: Option<TaskId>,
29}
30
31impl ExecutionScope {
32    pub fn new(
33        tenant_id: impl Into<String>,
34        user_id: impl Into<String>,
35        session_id: impl Into<String>,
36        thread_id: impl Into<String>,
37    ) -> Self {
38        Self {
39            tenant_id: tenant_id.into(),
40            user_id: user_id.into(),
41            session_id: session_id.into(),
42            thread_id: thread_id.into(),
43            task_id: None,
44        }
45    }
46
47    #[must_use = "builder methods return the modified builder"]
48    pub fn with_task(mut self, task_id: impl Into<String>) -> Self {
49        self.task_id = Some(task_id.into());
50        self
51    }
52
53    /// SurrealDB namespace for this scope — tenant-level isolation.
54    ///
55    /// Maps to `db.use_ns(scope.namespace())`. Each tenant gets a separate
56    /// SurrealDB namespace — complete isolation at the storage layer.
57    /// No query can cross namespace boundaries.
58    ///
59    /// Used by `SurrealCheckpointer` at construction time and
60    /// by `SurrealMemoryStore` per-call via `scoped()`.
61    pub fn namespace(&self) -> &str {
62        &self.tenant_id
63    }
64
65    /// Database name within the namespace.
66    ///
67    /// Maps to `db.use_db(scope.database())`. Fixed per deployment —
68    /// all agents share one database within a tenant namespace.
69    /// Table-level scoping (thread_id, user_id, session_id) provides
70    /// finer isolation within the database.
71    pub fn database(&self) -> &str {
72        "potential_expectations"
73    }
74}