ijima_core/namespace.rs
1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Memory namespaces — the isolation boundary for multi-user access (D2).
5//!
6//! Every read and write through the [`crate::Store`] is scoped to a
7//! [`NamespaceId`]. A namespace is the unit of visibility: an operator's
8//! private memory, a shared project namespace, or the global commons
9//! (the legacy pi-mempalace "everyone sees everything" mode).
10
11/// Identifies a memory namespace.
12///
13/// Wire form is a stable opaque string (e.g. `ns_elliott_private`,
14/// `ns_ijima_shared`, `ns_global`). v0 stores this as a SurrealDB record
15/// field; a future promotion maps each namespace onto a native SurrealDB
16/// `NS`/`DB` scope for first-class isolation.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(feature = "serde", serde(transparent))]
20pub struct NamespaceId(pub String);
21
22impl NamespaceId {
23 /// Construct a namespace id from any string-like value.
24 pub fn new(id: impl Into<String>) -> Self {
25 Self(id.into())
26 }
27
28 /// The stable wire string.
29 pub fn as_str(&self) -> &str {
30 &self.0
31 }
32}
33
34/// The visibility/isolation class of a namespace.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub enum NamespaceKind {
38 /// One operator's personal memory — visible only to that operator.
39 Private,
40 /// A project/team namespace — visible to a configured group.
41 Shared,
42 /// The global commons — visible to everyone (legacy pi-mempalace mode).
43 Global,
44}
45
46/// A namespace descriptor: its id, visibility class, and owning operator.
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct Namespace {
50 /// Stable opaque identifier.
51 pub id: NamespaceId,
52 /// Visibility class.
53 pub kind: NamespaceKind,
54 /// Owning operator principal id (`"system"` for the global commons).
55 pub owner: String,
56}
57
58/// The global doctrine namespace — curated, Git-versioned memories
59/// ([`crate::MemorySource::Doctrine`]) mirrored from the seed pack.
60/// Readable by every principal via `?namespace=ns_doctrine`.
61pub const DOCTRINE_NAMESPACE: &str = "ns_doctrine";
62
63/// A principal's membership in a shared namespace — the WS3 org-wall
64/// grant. Membership is store-backed (mutable at runtime, no redeploy);
65/// see the namespace-membership ADR.
66#[derive(Debug, Clone, PartialEq, Eq)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68pub struct NamespaceMembership {
69 /// The shared namespace (`ns_<org>_shared`).
70 pub namespace: String,
71 /// The principal granted access.
72 pub principal: String,
73 /// Unix seconds when the grant was recorded.
74 pub granted_at_unix: u64,
75 /// The admin principal who issued the grant (audit trail).
76 pub granted_by: String,
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn namespace_id_round_trips() {
85 let ns = NamespaceId::new("ns_elliott_private");
86 assert_eq!(ns.as_str(), "ns_elliott_private");
87 assert_eq!(ns, NamespaceId("ns_elliott_private".into()));
88 }
89
90 #[test]
91 fn global_namespace_owner_is_system() {
92 let global = Namespace {
93 id: NamespaceId::new("ns_global"),
94 kind: NamespaceKind::Global,
95 owner: "system".into(),
96 };
97 assert_eq!(global.kind, NamespaceKind::Global);
98 assert_eq!(global.owner, "system");
99 }
100}