Skip to main content

tandem_memory/
governance.rs

1use serde::{Deserialize, Serialize};
2
3/// Governance-facing tier model for scoped memory access.
4///
5/// Note: `team` and `curated` are included for policy/capability contracts
6/// before storage-layer migrations complete.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum GovernedMemoryTier {
10    Session,
11    Project,
12    Team,
13    Curated,
14}
15
16impl std::fmt::Display for GovernedMemoryTier {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            Self::Session => write!(f, "session"),
20            Self::Project => write!(f, "project"),
21            Self::Team => write!(f, "team"),
22            Self::Curated => write!(f, "curated"),
23        }
24    }
25}
26
27/// Hard partition for memory operations in corporate/LAN environments.
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct MemoryPartition {
30    pub org_id: String,
31    pub workspace_id: String,
32    pub project_id: String,
33    pub tier: GovernedMemoryTier,
34}
35
36impl MemoryPartition {
37    pub fn key(&self) -> String {
38        format!(
39            "{}/{}/{}/{}",
40            self.org_id, self.workspace_id, self.project_id, self.tier
41        )
42    }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum MemoryClassification {
48    Internal,
49    Restricted,
50}
51
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct MemoryCapabilities {
54    #[serde(default)]
55    pub read_tiers: Vec<GovernedMemoryTier>,
56    #[serde(default)]
57    pub write_tiers: Vec<GovernedMemoryTier>,
58    #[serde(default)]
59    pub promote_targets: Vec<GovernedMemoryTier>,
60    #[serde(default = "default_require_review_for_promote")]
61    pub require_review_for_promote: bool,
62    #[serde(default)]
63    pub allow_auto_use_tiers: Vec<GovernedMemoryTier>,
64}
65
66fn default_require_review_for_promote() -> bool {
67    true
68}
69
70impl Default for MemoryCapabilities {
71    fn default() -> Self {
72        Self {
73            read_tiers: vec![GovernedMemoryTier::Session, GovernedMemoryTier::Project],
74            write_tiers: vec![GovernedMemoryTier::Session],
75            promote_targets: Vec::new(),
76            require_review_for_promote: true,
77            allow_auto_use_tiers: vec![GovernedMemoryTier::Curated],
78        }
79    }
80}
81
82/// Run-scoped capability token claims for memory access.
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct MemoryCapabilityToken {
85    pub run_id: String,
86    pub subject: String,
87    pub org_id: String,
88    pub workspace_id: String,
89    pub project_id: String,
90    pub memory: MemoryCapabilities,
91    pub expires_at: u64,
92}
93
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95#[serde(rename_all = "snake_case")]
96pub enum MemoryContentKind {
97    SolutionCapsule,
98    Note,
99    Fact,
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103pub struct MemoryPutRequest {
104    pub run_id: String,
105    pub partition: MemoryPartition,
106    pub kind: MemoryContentKind,
107    pub content: String,
108    #[serde(default)]
109    pub artifact_refs: Vec<String>,
110    pub classification: MemoryClassification,
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub metadata: Option<serde_json::Value>,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct MemoryPutResponse {
117    pub id: String,
118    pub stored: bool,
119    pub tier: GovernedMemoryTier,
120    pub partition_key: String,
121    pub audit_id: String,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct PromotionReview {
126    pub required: bool,
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub reviewer_id: Option<String>,
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub approval_id: Option<String>,
131}
132
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134pub struct MemoryPromoteRequest {
135    pub run_id: String,
136    pub source_memory_id: String,
137    pub from_tier: GovernedMemoryTier,
138    pub to_tier: GovernedMemoryTier,
139    pub partition: MemoryPartition,
140    pub reason: String,
141    pub review: PromotionReview,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
145#[serde(rename_all = "snake_case")]
146pub enum ScrubStatus {
147    Passed,
148    Redacted,
149    Blocked,
150}
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
153pub struct ScrubReport {
154    pub status: ScrubStatus,
155    pub redactions: u32,
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub block_reason: Option<String>,
158}
159
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
161pub struct MemoryPromoteResponse {
162    pub promoted: bool,
163    #[serde(default, skip_serializing_if = "Option::is_none")]
164    pub new_memory_id: Option<String>,
165    pub to_tier: GovernedMemoryTier,
166    pub scrub_report: ScrubReport,
167    pub audit_id: String,
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171pub struct MemorySearchRequest {
172    pub run_id: String,
173    pub query: String,
174    #[serde(default)]
175    pub read_scopes: Vec<GovernedMemoryTier>,
176    pub partition: MemoryPartition,
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub limit: Option<i64>,
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct MemorySearchResponse {
183    #[serde(default)]
184    pub results: Vec<serde_json::Value>,
185    #[serde(default)]
186    pub scopes_used: Vec<GovernedMemoryTier>,
187    #[serde(default)]
188    pub blocked_scopes: Vec<GovernedMemoryTier>,
189    pub audit_id: String,
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn default_capabilities_are_fail_safe() {
198        let caps = MemoryCapabilities::default();
199        assert_eq!(
200            caps.read_tiers,
201            vec![GovernedMemoryTier::Session, GovernedMemoryTier::Project]
202        );
203        assert_eq!(caps.write_tiers, vec![GovernedMemoryTier::Session]);
204        assert!(caps.promote_targets.is_empty());
205        assert!(caps.require_review_for_promote);
206        assert_eq!(caps.allow_auto_use_tiers, vec![GovernedMemoryTier::Curated]);
207    }
208
209    #[test]
210    fn partition_key_is_stable() {
211        let partition = MemoryPartition {
212            org_id: "org_acme".to_string(),
213            workspace_id: "ws_tandem".to_string(),
214            project_id: "proj_engine".to_string(),
215            tier: GovernedMemoryTier::Project,
216        };
217        assert_eq!(
218            partition.key(),
219            "org_acme/ws_tandem/proj_engine/project".to_string()
220        );
221    }
222}