pe-core 0.1.0

Core types for Potential Expectations — messages, channels, state, traits
Documentation
//! ExecutionScope — multi-tenant isolation.
//!
//! Every operation carries a scope. No operation happens without one.
//! Based on Group 27 of the pre-plan.

use serde::{Deserialize, Serialize};

pub type TenantId = String;
pub type UserId = String;
pub type SessionId = String;
pub type ThreadId = String;
pub type TaskId = String;

/// Execution scope — flows through every storage operation.
///
/// Isolation rules:
/// - tenant_id:  complete isolation. Tenant A never sees Tenant B.
/// - user_id:    within tenant, users share collective but not private memory.
/// - session_id: within user, sessions share long-term memory but not in-progress state.
/// - thread_id:  within session, threads are independent conversations.
/// - task_id:    within thread, tasks track specific work items.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionScope {
    pub tenant_id: TenantId,
    pub user_id: UserId,
    pub session_id: SessionId,
    pub thread_id: ThreadId,
    pub task_id: Option<TaskId>,
}

impl ExecutionScope {
    pub fn new(
        tenant_id: impl Into<String>,
        user_id: impl Into<String>,
        session_id: impl Into<String>,
        thread_id: impl Into<String>,
    ) -> Self {
        Self {
            tenant_id: tenant_id.into(),
            user_id: user_id.into(),
            session_id: session_id.into(),
            thread_id: thread_id.into(),
            task_id: None,
        }
    }

    #[must_use = "builder methods return the modified builder"]
    pub fn with_task(mut self, task_id: impl Into<String>) -> Self {
        self.task_id = Some(task_id.into());
        self
    }

    /// SurrealDB namespace for this scope — tenant-level isolation.
    ///
    /// Maps to `db.use_ns(scope.namespace())`. Each tenant gets a separate
    /// SurrealDB namespace — complete isolation at the storage layer.
    /// No query can cross namespace boundaries.
    ///
    /// Used by `SurrealCheckpointer` at construction time and
    /// by `SurrealMemoryStore` per-call via `scoped()`.
    pub fn namespace(&self) -> &str {
        &self.tenant_id
    }

    /// Database name within the namespace.
    ///
    /// Maps to `db.use_db(scope.database())`. Fixed per deployment —
    /// all agents share one database within a tenant namespace.
    /// Table-level scoping (thread_id, user_id, session_id) provides
    /// finer isolation within the database.
    pub fn database(&self) -> &str {
        "potential_expectations"
    }
}