1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex};
3
4use serde::{ser::SerializeStruct, Deserialize, Serialize};
5
6#[derive(Clone, Copy, Default)]
7pub struct DbState;
8
9#[derive(Clone, Default)]
10pub(super) struct StatusCache {
11 pub(super) entry: Arc<Mutex<Option<StatusCacheEntry>>>,
12}
13
14#[derive(Clone)]
15pub(super) struct StatusCacheEntry {
16 pub(super) generated_at_epoch: i64,
17 pub(super) payload: serde_json::Value,
18}
19
20#[derive(Deserialize, Default)]
21pub(super) struct StatusParams {
22 pub refresh: Option<bool>,
23}
24
25#[derive(Deserialize)]
26pub(super) struct SearchParams {
27 pub query: Option<String>,
28 pub project: Option<String>,
29 #[serde(rename = "type")]
30 pub memory_type: Option<String>,
31 pub limit: Option<i64>,
32 pub offset: Option<i64>,
33 pub include_stale: Option<bool>,
34 pub include_suppressed: Option<bool>,
35 pub branch: Option<String>,
36 pub multi_hop: Option<bool>,
37 pub explain: Option<bool>,
38}
39
40#[derive(Serialize)]
41pub(super) struct SearchResponse {
42 pub data: Vec<MemoryItem>,
43 pub meta: Meta,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub multi_hop: Option<MultiHopInfo>,
46 #[serde(skip_serializing_if = "Vec::is_empty")]
49 pub raw_hits: Vec<RawHitItem>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub raw_hits_error: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub explain: Option<crate::retrieval::search::SearchExplainDetails>,
54}
55
56#[derive(Serialize)]
57pub(super) struct RawHitItem {
58 pub id: i64,
59 pub session_id: String,
60 pub project: String,
61 pub role: String,
62 pub preview: String,
63 pub source: String,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub branch: Option<String>,
66 pub created_at_epoch: i64,
67}
68
69#[derive(Serialize)]
70pub(super) struct MultiHopInfo {
71 pub hops: u8,
72 pub entities_discovered: Vec<String>,
73}
74
75#[derive(Serialize)]
76pub(super) struct MemoryItem {
77 pub id: i64,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub version: Option<i64>,
80 pub title: String,
81 pub content: String,
82 pub memory_type: String,
83 pub project: String,
84 pub scope: String,
85 pub status: String,
86 pub classification: crate::truth::MemoryVisibilityClass,
87 pub current_context_eligible: bool,
88 pub classification_reason: String,
89 pub staleness: crate::memory::MemoryStalenessLabel,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub topic_key: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub branch: Option<String>,
94 pub created_at_epoch: i64,
95 pub updated_at_epoch: i64,
96}
97
98#[derive(Serialize)]
99pub(super) struct Meta {
100 pub count: usize,
101 pub has_more: bool,
102 pub limit: i64,
103 pub offset: i64,
104}
105
106#[derive(Serialize)]
107pub(super) struct ErrorResponse {
108 pub error: ErrorDetail,
109}
110
111#[derive(Serialize)]
112pub(super) struct ErrorDetail {
113 pub code: String,
114 pub message: String,
115}
116
117#[derive(Serialize)]
118pub(super) struct CapabilitiesResponse {
119 pub version: &'static str,
120 pub schema_version: i64,
121 pub api_version: u16,
122 pub features: CapabilitiesFeatures,
123 pub endpoints: BTreeMap<&'static str, &'static str>,
124}
125
126#[derive(Serialize)]
127pub(super) struct CapabilitiesFeatures {
128 pub health: bool,
129 pub status: bool,
130 pub stats: bool,
131 pub search: bool,
132 pub search_explain: bool,
133 pub memory_list: bool,
134 pub memory_detail: bool,
135 pub save_memory: bool,
136 pub memory_archive: bool,
137 pub memory_restore: bool,
138 pub memory_delete: bool,
139 pub candidate_rows: bool,
140 pub candidate_filters: bool,
141 pub candidate_review: bool,
142 pub candidate_detail: bool,
143 pub candidate_evidence: bool,
144 pub candidate_review_safe: bool,
145 pub observations: bool,
146 pub sessions: bool,
147 pub workstreams: bool,
148 pub events: bool,
149 pub tasks: bool,
150 pub graph: bool,
151 pub user_recall: bool,
152 pub user_recall_usage_policy: bool,
153}
154
155#[derive(Serialize)]
156pub(super) struct HealthResponse {
157 pub ok: bool,
158 pub version: &'static str,
159 pub api_version: u16,
160 pub schema_version: i64,
161}
162
163#[derive(Deserialize)]
164pub(super) struct SaveMemoryRequest {
165 pub text: String,
166 #[serde(default)]
167 pub title: Option<String>,
168 #[serde(default)]
169 pub project: Option<String>,
170 #[serde(default)]
171 pub session_id: Option<String>,
172 #[serde(default)]
173 pub host: Option<String>,
174 #[serde(default)]
175 pub topic_key: Option<String>,
176 #[serde(default)]
177 pub memory_type: Option<String>,
178 #[serde(default)]
179 pub files: Option<Vec<String>>,
180 #[serde(default)]
181 pub scope: Option<String>,
182 #[serde(default)]
183 pub reference_time_epoch: Option<i64>,
184 #[serde(default)]
185 pub created_at_epoch: Option<i64>,
186 #[serde(default)]
187 pub branch: Option<String>,
188 #[serde(default)]
189 pub local_path: Option<String>,
190 #[serde(default)]
191 pub local_copy_enabled: Option<bool>,
192 #[serde(default)]
193 pub claim_enabled: Option<bool>,
194 #[serde(default)]
195 pub claim_source: Option<String>,
196 #[serde(default)]
197 pub acknowledge_pattern: Option<String>,
198}
199
200#[derive(Deserialize)]
201pub(super) struct UserRecallRequest {
202 pub query: String,
203 #[serde(default)]
204 pub project: Option<String>,
205 #[serde(default)]
206 pub cwd: Option<String>,
207 #[serde(default)]
208 pub task_intent: Option<String>,
209 #[serde(default)]
210 pub current_files: Vec<String>,
211 #[serde(default)]
212 pub host: Option<String>,
213 #[serde(default)]
214 pub owner_scope: Option<String>,
215 #[serde(default)]
216 pub owner_key: Option<String>,
217 #[serde(default)]
218 pub state_keys: Vec<String>,
219 #[serde(default)]
220 pub include_sensitive: bool,
221 #[serde(default)]
222 pub include_suppressed: bool,
223 #[serde(default)]
224 pub limit: Option<i64>,
225 #[serde(default)]
226 pub budget_chars: Option<usize>,
227}
228
229#[derive(Serialize)]
230pub(super) struct SaveMemoryResponse {
231 pub id: i64,
232 pub status: String,
233 pub memory_type: String,
234 pub project: String,
235 pub scope: String,
236 pub topic_key: Option<String>,
237 pub branch: Option<String>,
238 pub operation: String,
239 pub created_at_epoch: i64,
240 pub reference_time_epoch: i64,
241 pub updated_at_epoch: i64,
242 pub upserted: bool,
243 pub local_copy: LocalCopyResponse,
244 pub local_status: String,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub local_path: Option<String>,
247 pub claim_status: String,
248 pub claim_id: Option<i64>,
249 pub claim_error: Option<String>,
250 pub next_step: SaveMemoryNextStepResponse,
251}
252
253#[derive(Serialize)]
254pub(super) struct LocalCopyResponse {
255 pub status: String,
256 pub path: Option<String>,
257 #[serde(skip_serializing_if = "Option::is_none")]
258 pub reason: Option<String>,
259}
260
261#[derive(Serialize)]
262pub(super) struct SaveMemoryNextStepResponse {
263 pub tool: String,
264 pub ids: Vec<i64>,
265 pub source: String,
266 pub reason: String,
267}
268
269#[derive(Deserialize)]
270pub(super) struct ShowParams {
271 pub id: i64,
272 pub include_suppressed: Option<bool>,
273}
274
275#[derive(Deserialize)]
276pub(super) struct MemoryDetailParams {
277 pub include_suppressed: Option<bool>,
278}
279#[derive(Deserialize)]
282pub(super) struct ListParams {
283 pub project: Option<String>,
284 #[serde(rename = "type")]
285 pub memory_type: Option<String>,
286 pub scope: Option<String>,
287 pub status: Option<String>,
288 pub branch: Option<String>,
289 pub q: Option<String>,
290 pub include_suppressed: Option<bool>,
291 pub limit: Option<i64>,
292 pub offset: Option<i64>,
293}
294
295pub(super) struct ListMeta {
296 pub count: usize,
297 pub total: i64,
298 pub limit: i64,
299 pub offset: i64,
300}
301
302impl Serialize for ListMeta {
303 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
304 where
305 S: serde::Serializer,
306 {
307 let count = i64::try_from(self.count).unwrap_or(i64::MAX);
308 let consumed = self.offset.saturating_add(count);
309 let has_more = consumed < self.total;
310 let next_offset = has_more.then_some(consumed);
311
312 let mut state = serializer.serialize_struct("ListMeta", 6)?;
313 state.serialize_field("count", &self.count)?;
314 state.serialize_field("total", &self.total)?;
315 state.serialize_field("limit", &self.limit)?;
316 state.serialize_field("offset", &self.offset)?;
317 state.serialize_field("has_more", &has_more)?;
318 state.serialize_field("next_offset", &next_offset)?;
319 state.end()
320 }
321}
322
323#[derive(Serialize)]
324pub(super) struct ListResponse<T: Serialize> {
325 pub data: Vec<T>,
326 pub meta: ListMeta,
327}
328
329#[derive(Deserialize)]
330pub(super) struct CandidateParams {
331 pub project: Option<String>,
332 pub status: Option<String>,
333 #[serde(rename = "type")]
334 pub memory_type: Option<String>,
335 pub block_reason: Option<String>,
336 pub topic_key: Option<String>,
337 pub contains: Option<String>,
338 pub min_confidence: Option<f64>,
339 pub older_than_days: Option<i64>,
340 pub limit: Option<i64>,
341 pub offset: Option<i64>,
342}
343
344#[derive(Deserialize)]
345pub(super) struct BlockedParams {
346 pub project: Option<String>,
347}
348
349#[derive(Serialize)]
350pub(super) struct BlockedReasonItem {
351 #[serde(skip_serializing_if = "Option::is_none")]
352 pub reason: Option<String>,
353 pub pending: i64,
354 pub example_ids: Vec<i64>,
355}
356
357#[derive(Serialize)]
358pub(super) struct CandidateItem {
359 pub id: i64,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub project: Option<String>,
362 pub memory_type: String,
363 pub text: String,
364 pub scope: String,
365 pub confidence: f64,
366 pub risk_class: String,
367 pub review_status: String,
368 pub evidence_count: i64,
369 pub created_at_epoch: i64,
370}
371
372#[derive(Deserialize)]
373pub(super) struct CandidateEditRequest {
374 #[serde(default)]
375 pub scope: Option<String>,
376 #[serde(default, rename = "memory_type")]
377 pub memory_type: Option<String>,
378 #[serde(default)]
379 pub topic_key: Option<String>,
380 #[serde(default)]
381 pub text: Option<String>,
382}
383
384#[derive(Deserialize, Default)]
385pub(super) struct CandidateApproveRequest {
386 #[serde(default)]
387 pub acknowledge_pattern: Option<String>,
388}
389
390#[derive(Serialize)]
391pub(super) struct CandidateReviewResponse {
392 pub candidate_id: i64,
393 pub status: String,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub memory_id: Option<i64>,
396}
397
398#[derive(Serialize)]
399pub(super) struct CandidateDetailResponse {
400 pub data: CandidateDetailItem,
401 pub evidence: Vec<CandidateEvidenceItem>,
402 #[serde(skip_serializing_if = "Option::is_none")]
403 pub provenance: Option<CandidateDreamProvenance>,
404 pub decision: CandidateReviewDecision,
405}
406
407#[derive(Serialize)]
408pub(super) struct CandidateDreamProvenance {
409 pub kind: &'static str,
410 pub review_token: Option<String>,
411 pub authorized_supersede_ids: Vec<i64>,
412 pub artifacts: Vec<CandidateDreamArtifact>,
413}
414
415#[derive(Serialize)]
416pub(super) struct CandidateDreamArtifact {
417 pub artifact_id: i64,
418 pub version: i64,
419 pub project: String,
420 pub cluster_signature: String,
421 pub member_ids: Vec<i64>,
422 pub decision_kind: String,
423 pub decision_ids: Vec<i64>,
424 pub decision_payload_sha256: String,
425 pub intended_superseded_ids: Vec<i64>,
426 pub generated_topic_key: Option<String>,
427 pub generated_memory_type: Option<String>,
428 pub generated_title: Option<String>,
429 pub generated_content: Option<String>,
430 pub generated_field: String,
431 pub pattern_id: String,
432 pub pattern_version: i64,
433 pub source_operation: String,
434 pub source_trust_class: String,
435 pub occurrence_count: i64,
436 pub created_at_epoch: i64,
437 pub updated_at_epoch: i64,
438}
439
440#[derive(Serialize)]
441pub(super) struct CandidateDetailItem {
442 pub id: i64,
443 pub project: Option<String>,
444 pub scope: String,
445 pub memory_type: String,
446 pub topic_key: String,
447 pub text: String,
448 pub source_kind: Option<String>,
449 pub source_project: Option<String>,
450 pub target_project: Option<String>,
451 pub owner_scope: Option<String>,
452 pub owner_key: Option<String>,
453 pub topic_domain: Option<String>,
454 pub routing_confidence: Option<f64>,
455 pub routing_reason: Option<String>,
456 pub context_class: Option<String>,
457 pub confidence: f64,
458 pub risk_class: String,
459 pub review_status: String,
460 pub auto_promote_block_reason: Option<String>,
461 pub source_trust_class: String,
462 pub quarantine_pattern_id: Option<String>,
463 pub quarantine_pattern_version: Option<i64>,
464 pub version: i64,
465 pub created_at_epoch: i64,
466 pub updated_at_epoch: i64,
467}
468
469#[derive(Serialize)]
470pub(super) struct CandidateEvidenceItem {
471 pub source_kind: &'static str,
472 pub source_id: i64,
473 pub event_type: Option<String>,
474 pub role: Option<String>,
475 pub tool_name: Option<String>,
476 pub created_at_epoch: Option<i64>,
477 pub summary: String,
478 pub preview: String,
479 pub provenance_status: String,
480 pub redacted: bool,
481}
482
483#[derive(Serialize)]
484pub(super) struct CandidateReviewDecision {
485 pub can_review: bool,
486 pub blocked_reasons: Vec<String>,
487 pub actions: CandidateReviewActionDecisions,
488}
489
490#[derive(Serialize)]
491pub(super) struct CandidateReviewActionDecisions {
492 pub approve: CandidateReviewActionDecision,
493 pub reject: CandidateReviewActionDecision,
494 pub edit: CandidateReviewActionDecision,
495}
496
497#[derive(Serialize)]
498pub(super) struct CandidateReviewActionDecision {
499 pub allowed: bool,
500 pub blocked_reasons: Vec<String>,
501}
502
503#[derive(Debug, Clone, Deserialize)]
504#[serde(deny_unknown_fields)]
505pub(super) struct CandidateSafeApproveRequest {
506 pub reason: String,
507 pub expected_version: i64,
508 pub idempotency_key: String,
509 #[serde(default)]
510 pub acknowledge_pattern: Option<String>,
511 #[serde(default)]
512 pub acknowledge_dream_review_token: Option<String>,
513}
514
515#[derive(Debug, Clone, Deserialize)]
516#[serde(deny_unknown_fields)]
517pub(super) struct CandidateSafeRejectRequest {
518 pub reason: String,
519 pub expected_version: i64,
520 pub idempotency_key: String,
521}
522
523#[derive(Debug, Clone, Deserialize)]
524#[serde(deny_unknown_fields)]
525pub(super) struct CandidateSafeEditRequest {
526 pub reason: String,
527 pub expected_version: i64,
528 pub idempotency_key: String,
529 #[serde(default)]
530 pub scope: Option<String>,
531 #[serde(default, rename = "memory_type")]
532 pub memory_type: Option<String>,
533 #[serde(default)]
534 pub topic_key: Option<String>,
535 #[serde(default)]
536 pub text: Option<String>,
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
540pub(super) struct CandidateSafeReviewResponse {
541 pub response_schema_version: i64,
542 pub operation_id: String,
543 pub audit_id: i64,
544 pub candidate_id: i64,
545 #[serde(skip_serializing_if = "Option::is_none")]
546 pub memory_id: Option<i64>,
547 pub action: String,
548 pub before_status: String,
549 pub after_status: String,
550 pub version: i64,
551 pub occurred_at_epoch: i64,
552 pub replayed: bool,
553}
554
555#[derive(Debug, Clone, Deserialize)]
556#[serde(deny_unknown_fields)]
557pub(super) struct MemorySafeGovernanceRequest {
558 pub reason: String,
559 pub expected_version: i64,
560 pub idempotency_key: String,
561}
562
563#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
564pub(super) struct MemorySafeGovernanceResponse {
565 pub response_schema_version: i64,
566 pub operation_id: String,
567 pub audit_id: i64,
568 pub memory_id: i64,
569 pub action: String,
570 pub before_status: String,
571 pub after_status: String,
572 pub version: i64,
573 pub occurred_at_epoch: i64,
574 pub replayed: bool,
575}
576
577#[derive(Serialize)]
578pub(super) struct SafeMutationErrorResponse {
579 pub error: SafeMutationErrorDetail,
580}
581
582#[derive(Serialize)]
583pub(super) struct SafeMutationErrorDetail {
584 pub code: String,
585 pub message: String,
586 #[serde(skip_serializing_if = "Option::is_none")]
587 pub operation_id: Option<String>,
588}
589
590#[derive(Deserialize)]
591pub(super) struct GraphParams {
592 pub project: Option<String>,
593 pub include_suppressed: Option<bool>,
594 pub limit: Option<i64>,
595}
596
597#[derive(Serialize)]
598pub(super) struct GraphNodeItem {
599 pub id: i64,
600 pub name: String,
601 pub entity_type: Option<String>,
602 pub mention_count: i64,
603 pub mems: Vec<i64>,
604}
605
606#[derive(Serialize)]
607pub(super) struct GraphEdgeItem {
608 pub a: i64,
609 pub b: i64,
610 pub w: i64,
611}
612
613#[derive(Serialize)]
614pub(super) struct GraphResponse {
615 pub nodes: Vec<GraphNodeItem>,
616 pub edges: Vec<GraphEdgeItem>,
617}
618
619#[derive(Serialize)]
620pub(super) struct MemoryEdgeItem {
621 pub id: i64,
622 pub edge_type: String,
623 pub from_memory_id: Option<i64>,
624 pub to_memory_id: Option<i64>,
625 pub confidence: Option<f64>,
626}
627
628#[derive(Serialize)]
629pub(super) struct MemoryDetailResponse {
630 #[serde(flatten)]
631 pub memory: MemoryItem,
632 pub entities: Vec<String>,
633 pub edges: Vec<MemoryEdgeItem>,
634}
635
636#[derive(Serialize)]
637pub(super) struct TypeCount {
638 pub memory_type: String,
639 pub count: i64,
640}
641
642#[derive(Serialize)]
643pub(super) struct StatsResponse {
644 pub active_memories: i64,
645 pub total_memories: i64,
646 pub pending_candidates: i64,
647 pub captured_events: i64,
648 pub pending_extraction_tasks: i64,
649 pub ai_calls: i64,
650 pub ai_cost_usd: f64,
651 pub ai_total_tokens: i64,
652 pub type_distribution: Vec<TypeCount>,
653}