subcog/models/
domain.rs

1//! Domain and namespace types.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Memory namespace categories.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
8#[serde(rename_all = "lowercase")]
9pub enum Namespace {
10    /// Architectural and design decisions.
11    #[default]
12    Decisions,
13    /// Discovered patterns and conventions.
14    Patterns,
15    /// Lessons learned from debugging or issues.
16    Learnings,
17    /// Important contextual information.
18    Context,
19    /// Technical debts and future improvements.
20    #[serde(alias = "techdebt", alias = "tech_debt")]
21    #[serde(rename = "tech-debt")]
22    TechDebt,
23    /// Blockers and impediments.
24    Blockers,
25    /// Work progress and milestones.
26    Progress,
27    /// API endpoints and contracts.
28    Apis,
29    /// Configuration and environment details.
30    Config,
31    /// Security-related information.
32    Security,
33    /// Performance optimizations and benchmarks.
34    Performance,
35    /// Testing strategies and edge cases.
36    Testing,
37    /// Built-in help content (read-only system namespace).
38    Help,
39}
40
41impl Namespace {
42    /// Returns all namespace variants.
43    #[must_use]
44    pub const fn all() -> &'static [Self] {
45        &[
46            Self::Decisions,
47            Self::Patterns,
48            Self::Learnings,
49            Self::Context,
50            Self::TechDebt,
51            Self::Blockers,
52            Self::Progress,
53            Self::Apis,
54            Self::Config,
55            Self::Security,
56            Self::Performance,
57            Self::Testing,
58            Self::Help,
59        ]
60    }
61
62    /// Returns user-facing namespaces (excludes system namespaces like Help).
63    #[must_use]
64    pub const fn user_namespaces() -> &'static [Self] {
65        &[
66            Self::Decisions,
67            Self::Patterns,
68            Self::Learnings,
69            Self::Context,
70            Self::TechDebt,
71            Self::Blockers,
72            Self::Progress,
73            Self::Apis,
74            Self::Config,
75            Self::Security,
76            Self::Performance,
77            Self::Testing,
78        ]
79    }
80
81    /// Returns the namespace as a string slice.
82    #[must_use]
83    pub const fn as_str(&self) -> &'static str {
84        match self {
85            Self::Decisions => "decisions",
86            Self::Patterns => "patterns",
87            Self::Learnings => "learnings",
88            Self::Context => "context",
89            Self::TechDebt => "tech-debt",
90            Self::Blockers => "blockers",
91            Self::Progress => "progress",
92            Self::Apis => "apis",
93            Self::Config => "config",
94            Self::Security => "security",
95            Self::Performance => "performance",
96            Self::Testing => "testing",
97            Self::Help => "help",
98        }
99    }
100
101    /// Returns true if this is a system namespace (read-only).
102    #[must_use]
103    pub const fn is_system(&self) -> bool {
104        matches!(self, Self::Help)
105    }
106
107    /// Parses a namespace from a string.
108    #[must_use]
109    pub fn parse(s: &str) -> Option<Self> {
110        match s.to_lowercase().as_str() {
111            "decisions" => Some(Self::Decisions),
112            "patterns" => Some(Self::Patterns),
113            "learnings" => Some(Self::Learnings),
114            "context" => Some(Self::Context),
115            "tech-debt" | "techdebt" | "tech_debt" => Some(Self::TechDebt),
116            "blockers" => Some(Self::Blockers),
117            "progress" => Some(Self::Progress),
118            "apis" => Some(Self::Apis),
119            "config" => Some(Self::Config),
120            "security" => Some(Self::Security),
121            "performance" => Some(Self::Performance),
122            "testing" => Some(Self::Testing),
123            "help" => Some(Self::Help),
124            _ => None,
125        }
126    }
127}
128
129impl fmt::Display for Namespace {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        write!(f, "{}", self.as_str())
132    }
133}
134
135/// Domain separation for memories.
136#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
137pub struct Domain {
138    /// Organization or team identifier.
139    pub organization: Option<String>,
140    /// Project identifier.
141    pub project: Option<String>,
142    /// Repository identifier.
143    pub repository: Option<String>,
144}
145
146impl Domain {
147    /// Creates a new domain with all fields empty.
148    #[must_use]
149    pub const fn new() -> Self {
150        Self {
151            organization: None,
152            project: None,
153            repository: None,
154        }
155    }
156
157    /// Creates a domain for a specific repository.
158    #[must_use]
159    pub fn for_repository(org: impl Into<String>, repo: impl Into<String>) -> Self {
160        Self {
161            organization: Some(org.into()),
162            project: None,
163            repository: Some(repo.into()),
164        }
165    }
166
167    /// Returns true if this is a global domain (no restrictions).
168    #[must_use]
169    pub const fn is_global(&self) -> bool {
170        self.organization.is_none() && self.project.is_none() && self.repository.is_none()
171    }
172
173    /// Returns the scope string for URN construction.
174    ///
175    /// - `"project"` for project-scoped (org + repo)
176    /// - `"org/{name}"` for organization-scoped
177    /// - `"global"` for global domain
178    #[must_use]
179    pub fn to_scope_string(&self) -> String {
180        match (&self.organization, &self.repository) {
181            (Some(org), Some(repo)) => format!("{org}/{repo}"),
182            (Some(org), None) => format!("org/{org}"),
183            (None, Some(repo)) => repo.clone(),
184            (None, None) => "project".to_string(),
185        }
186    }
187}
188
189impl fmt::Display for Domain {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        match (&self.organization, &self.project, &self.repository) {
192            (Some(org), Some(proj), Some(repo)) => write!(f, "{org}/{proj}/{repo}"),
193            (Some(org), None, Some(repo)) => write!(f, "{org}/{repo}"),
194            (Some(org), Some(proj), None) => write!(f, "{org}/{proj}"),
195            (Some(org), None, None) => write!(f, "{org}"),
196            (None, Some(proj), _) => write!(f, "{proj}"),
197            (None, None, Some(repo)) => write!(f, "{repo}"),
198            (None, None, None) => write!(f, "global"),
199        }
200    }
201}
202
203/// Status of a memory entry.
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
205pub enum MemoryStatus {
206    /// Active and searchable.
207    #[default]
208    Active,
209    /// Archived but still searchable.
210    Archived,
211    /// Superseded by another memory.
212    Superseded,
213    /// Pending review or approval.
214    Pending,
215    /// Marked for deletion.
216    Deleted,
217}
218
219impl MemoryStatus {
220    /// Returns the status as a string slice.
221    #[must_use]
222    pub const fn as_str(&self) -> &'static str {
223        match self {
224            Self::Active => "active",
225            Self::Archived => "archived",
226            Self::Superseded => "superseded",
227            Self::Pending => "pending",
228            Self::Deleted => "deleted",
229        }
230    }
231}
232
233impl fmt::Display for MemoryStatus {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        write!(f, "{}", self.as_str())
236    }
237}