返回 CodeWhale
model.rs
根目录 / crates / memory / src / model.rs
1 use crate::{Error, Result};
2 use serde::{Deserialize, Serialize};
3 use std::collections::BTreeMap;
4
5 macro_rules! string_enum {
6 ($name:ident { $($variant:ident => $value:literal),+ $(,)? }) => {
7 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
8 pub enum $name { $(#[serde(rename = $value)] $variant),+ }
9 impl $name {
10 pub fn as_str(self) -> &'static str { match self { $(Self::$variant => $value),+ } }
11 pub fn parse(s: &str) -> Result<Self> {
12 match s { $($value => Ok(Self::$variant)),+, _ => Err(Error::Invalid("unknown enum value".into())) }
13 }
14 }
15 }
16 }
17 string_enum!(Kind {
18 Preference => "preference", Fact => "fact", Decision => "decision",
19 Constraint => "constraint", Procedure => "procedure", Lesson => "lesson",
20 Commitment => "commitment", Episode => "episode", Handoff => "handoff"
21 });
22 string_enum!(Status { Candidate => "candidate", Active => "active", Stale => "stale", Superseded => "superseded", Rejected => "rejected" });
23 string_enum!(SourceKind { User => "user", Repository => "repository", Tool => "tool", Import => "import", Agent => "agent" });
24 string_enum!(Relation { Related => "related", Supports => "supports", Contradicts => "contradicts", DecisionOutcome => "decision_outcome" });
25 string_enum!(Freshness { Current => "current", Unknown => "unknown", Changed => "changed", Expired => "expired", Inactive => "inactive" });
26
27 /// Scope IDs are opaque, host-assigned identities, NOT filesystem paths or ACLs.
28 /// Every grant is an exact scope match; no prefix or wildcard matching occurs.
29 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
30 #[serde(deny_unknown_fields)]
31 pub struct Scope {
32 pub tenant: String,
33 pub user: String,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub workspace: Option<String>,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub branch: Option<String>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub session: Option<String>,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub agent: Option<String>,
42 }
43 impl Scope {
44 pub fn user(tenant: impl Into<String>, user: impl Into<String>) -> Self {
45 Self {
46 tenant: tenant.into(),
47 user: user.into(),
48 workspace: None,
49 branch: None,
50 session: None,
51 agent: None,
52 }
53 }
54 pub fn workspace(&self, id: impl Into<String>) -> Self {
55 Self {
56 workspace: Some(id.into()),
57 branch: None,
58 session: None,
59 agent: None,
60 ..self.clone()
61 }
62 }
63 pub fn branch(&self, id: impl Into<String>) -> Self {
64 Self {
65 branch: Some(id.into()),
66 session: None,
67 agent: None,
68 ..self.clone()
69 }
70 }
71 pub fn session(&self, id: impl Into<String>) -> Self {
72 Self {
73 session: Some(id.into()),
74 agent: None,
75 ..self.clone()
76 }
77 }
78 pub fn agent(&self, id: impl Into<String>) -> Self {
79 Self {
80 agent: Some(id.into()),
81 ..self.clone()
82 }
83 }
84 pub fn key(&self) -> Result<String> {
85 Ok(serde_json::to_string(self)?)
86 }
87 pub fn validate(&self) -> Result<()> {
88 for value in std::iter::once(&self.tenant)
89 .chain(std::iter::once(&self.user))
90 .chain(self.workspace.iter())
91 .chain(self.branch.iter())
92 .chain(self.session.iter())
93 .chain(self.agent.iter())
94 {
95 if value.trim().is_empty() || value.len() > 256 || value.chars().any(char::is_control) {
96 return Err(Error::Invalid(
97 "scope identities must be nonempty, bounded, and printable".into(),
98 ));
99 }
100 }
101 if (self.branch.is_some() && self.workspace.is_none())
102 || (self.agent.is_some() && self.session.is_none())
103 {
104 return Err(Error::Invalid(
105 "branch requires workspace; agent requires session".into(),
106 ));
107 }
108 Ok(())
109 }
110 }
111
112 #[derive(Debug, Clone, Serialize, Deserialize)]
113 #[serde(deny_unknown_fields)]
114 pub struct Evidence {
115 pub kind: SourceKind,
116 pub uri: String,
117 #[serde(default)]
118 pub locator: String,
119 #[serde(default)]
120 pub sha256: Option<String>,
121 /// Unix seconds; observation time is evidence metadata, not an authority grant.
122 pub observed_at: i64,
123 }
124
125 #[derive(Debug, Clone, Serialize, Deserialize)]
126 #[serde(deny_unknown_fields)]
127 pub struct Draft {
128 pub scope: Scope,
129 pub kind: Kind,
130 pub title: String,
131 pub body: String,
132 #[serde(default)]
133 pub key: Option<String>,
134 #[serde(default)]
135 pub tags: Vec<String>,
136 #[serde(default = "default_confidence")]
137 pub confidence: f64,
138 #[serde(default = "default_importance")]
139 pub importance: f64,
140 pub evidence: Vec<Evidence>,
141 #[serde(default)]
142 pub repository_revision: Option<String>,
143 #[serde(default)]
144 pub dependencies: BTreeMap<String, String>,
145 #[serde(default)]
146 pub expires_at: Option<i64>,
147 /// Valid time (Unix seconds), separate from recorded/knowledge time.
148 #[serde(default)]
149 pub valid_from: Option<i64>,
150 #[serde(default)]
151 pub valid_until: Option<i64>,
152 #[serde(default)]
153 pub parent_ids: Vec<String>,
154 }
155 fn default_confidence() -> f64 {
156 0.5
157 }
158 fn default_importance() -> f64 {
159 0.5
160 }
161 impl Draft {
162 pub fn note(
163 scope: Scope,
164 title: impl Into<String>,
165 body: impl Into<String>,
166 evidence: Evidence,
167 ) -> Self {
168 Self {
169 scope,
170 kind: Kind::Fact,
171 title: title.into(),
172 body: body.into(),
173 key: None,
174 tags: vec![],
175 confidence: 0.5,
176 importance: 0.5,
177 evidence: vec![evidence],
178 repository_revision: None,
179 dependencies: BTreeMap::new(),
180 expires_at: None,
181 valid_from: None,
182 valid_until: None,
183 parent_ids: vec![],
184 }
185 }
186 }
187 #[derive(Debug, Clone, Serialize, Deserialize)]
188 pub struct Memory {
189 pub id: String,
190 pub revision: i64,
191 pub status: Status,
192 pub draft: Draft,
193 pub created_at: i64,
194 pub updated_at: i64,
195 pub content_hash: String,
196 }
197 #[derive(Debug, Clone, Serialize, Deserialize)]
198 pub struct CaptureReceipt {
199 pub memory: Memory,
200 pub created: bool,
201 }
202 #[derive(Debug, Clone, Serialize, Deserialize)]
203 #[serde(deny_unknown_fields)]
204 pub struct ValidationReceipt {
205 pub content_hash: String,
206 pub validator: String,
207 pub evidence_uri: String,
208 pub passed: bool,
209 }
210 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
211 #[serde(deny_unknown_fields)]
212 pub struct Snapshot {
213 pub revision: Option<String>,
214 #[serde(default)]
215 pub files: BTreeMap<String, String>,
216 }
217 impl Memory {
218 pub fn freshness(&self, snapshot: &Snapshot, now: i64) -> Freshness {
219 if self.draft.valid_from.is_some_and(|t| t > now) {
220 return Freshness::Unknown;
221 }
222 if self.draft.valid_until.is_some_and(|t| t <= now) {
223 return Freshness::Expired;
224 }
225 if self.draft.expires_at.is_some_and(|t| t <= now) {
226 return Freshness::Expired;
227 }
228 if !matches!(self.status, Status::Active | Status::Stale) {
229 return Freshness::Inactive;
230 }
231 if self.status == Status::Stale {
232 return Freshness::Changed;
233 }
234 if !self.draft.dependencies.is_empty() {
235 let mut unknown = false;
236 for (path, digest) in &self.draft.dependencies {
237 match snapshot.files.get(path) {
238 Some(current) if current == digest => (),
239 Some(_) => return Freshness::Changed,
240 None => unknown = true,
241 }
242 }
243 return if unknown {
244 Freshness::Unknown
245 } else {
246 Freshness::Current
247 };
248 }
249 match (&self.draft.repository_revision, &snapshot.revision) {
250 (None, _) => Freshness::Current,
251 (Some(_), None) => Freshness::Unknown,
252 (Some(old), Some(current)) if old == current => Freshness::Current,
253 _ => Freshness::Changed,
254 }
255 }
256 }
257 #[derive(Debug, Clone, Serialize, Deserialize)]
258 #[serde(deny_unknown_fields)]
259 pub struct Embedding {
260 pub model: String,
261 pub vector: Vec<f32>,
262 }
263 #[derive(Debug, Clone)]
264 pub struct Recall {
265 pub query: String,
266 pub limit: usize,
267 pub snapshot: Snapshot,
268 pub include_stale: bool,
269 pub embedding: Option<Embedding>,
270 pub vector_scan_limit: usize,
271 pub expand_graph: bool,
272 }
273 impl Default for Recall {
274 fn default() -> Self {
275 Self {
276 query: String::new(),
277 limit: 12,
278 snapshot: Snapshot::default(),
279 include_stale: false,
280 embedding: None,
281 vector_scan_limit: 10_000,
282 expand_graph: true,
283 }
284 }
285 }
286 #[derive(Debug, Clone, Serialize, Deserialize)]
287 pub struct Hit {
288 pub memory: Memory,
289 pub freshness: Freshness,
290 pub score: f64,
291 pub reasons: Vec<String>,
292 }
293 #[derive(Debug, Clone, Serialize, Deserialize)]
294 pub struct RecallReport {
295 pub hits: Vec<Hit>,
296 pub vector_candidates: usize,
297 pub vector_scan_truncated: bool,
298 }
299 #[derive(Debug, Clone, Serialize, Deserialize)]
300 pub struct ForgetReport {
301 pub memories_deleted: usize,
302 pub checkpoints_deleted: usize,
303 pub wal_truncated: bool,
304 pub physical_erasure_guaranteed: bool,
305 }
306 #[derive(Debug, Clone, Serialize, Deserialize)]
307 #[serde(deny_unknown_fields)]
308 pub struct PendingOperation {
309 pub operation_id: String,
310 pub state: String,
311 }
312 #[derive(Debug, Clone, Serialize, Deserialize)]
313 #[serde(deny_unknown_fields)]
314 pub struct WorkingState {
315 pub summary: String,
316 #[serde(default)]
317 pub next_steps: Vec<String>,
318 #[serde(default)]
319 pub artifact_refs: Vec<String>,
320 #[serde(default)]
321 pub pending_operations: Vec<PendingOperation>,
322 }
323 #[derive(Debug, Clone, Serialize, Deserialize)]
324 #[serde(deny_unknown_fields)]
325 pub struct CheckpointDraft {
326 pub scope: Scope,
327 pub key: String,
328 pub state: WorkingState,
329 #[serde(default)]
330 pub memory_ids: Vec<String>,
331 #[serde(default)]
332 pub expires_at: Option<i64>,
333 }
334 #[derive(Debug, Clone, Serialize, Deserialize)]
335 pub struct MemoryRef {
336 pub id: String,
337 pub revision: i64,
338 pub content_hash: String,
339 }
340 #[derive(Debug, Clone, Serialize, Deserialize)]
341 pub struct Checkpoint {
342 pub id: String,
343 pub revision: i64,
344 pub draft: CheckpointDraft,
345 pub memories: Vec<MemoryRef>,
346 pub updated_at: i64,
347 pub expires_at: i64,
348 }
349 #[derive(Debug, Clone, Serialize, Deserialize)]
350 pub struct Resume {
351 pub checkpoint: Checkpoint,
352 pub invalidated_memory_ids: Vec<String>,
353 /// A checkpoint can NEVER restore approvals, permissions or completed side effects.
354 pub reconcile_pending_operations: bool,
355 }
356
356 lines RUST