use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryDimensionSpec {
pub id: String,
pub kind: String,
pub title: Option<String>,
pub metadata: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryCoordinateSpec {
pub dimension: String,
pub scope_id: String,
pub occurred_at: Option<String>,
pub sequence: Option<u32>,
pub rank: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryEntrySpec {
pub id: String,
pub kind: String,
pub text: String,
pub coordinates: Vec<MemoryCoordinateSpec>,
pub metadata: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryEvidenceSpec {
pub id: String,
pub supports: Vec<String>,
pub text: String,
pub source: Option<String>,
pub time: Option<String>,
pub metadata: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryRelationSpec {
pub from: String,
pub to: String,
pub rel: String,
pub semantic_class: String,
pub why: Option<String>,
pub confidence: Option<String>,
pub sequence: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryProvenanceSpec {
pub source_kind: String,
pub source_agent: String,
pub observed_at: String,
pub correlation_id: Option<String>,
pub causation_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryRecordRequest {
pub about: String,
pub dimensions: Vec<MemoryDimensionSpec>,
pub entries: Vec<MemoryEntrySpec>,
pub relations: Vec<MemoryRelationSpec>,
pub evidence: Vec<MemoryEvidenceSpec>,
pub provenance: Option<MemoryProvenanceSpec>,
pub idempotency_key: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_record_request_survives_the_wire() {
let request = MemoryRecordRequest {
about: "project:checkout".to_string(),
dimensions: vec![MemoryDimensionSpec {
id: "timeline:work".to_string(),
kind: "timeline".to_string(),
title: None,
metadata: BTreeMap::new(),
}],
entries: vec![MemoryEntrySpec {
id: "observation:first".to_string(),
kind: "observation".to_string(),
text: "The retries began after the deploy.".to_string(),
coordinates: vec![MemoryCoordinateSpec {
dimension: "timeline".to_string(),
scope_id: "timeline:work".to_string(),
occurred_at: Some("2026-08-04T10:00:00Z".to_string()),
sequence: Some(1),
rank: Some(1),
}],
metadata: BTreeMap::new(),
}],
relations: vec![MemoryRelationSpec {
from: "evidence:first".to_string(),
to: "observation:first".to_string(),
rel: "supports".to_string(),
semantic_class: "evidential".to_string(),
why: Some("the reading backs the observation".to_string()),
confidence: Some("high".to_string()),
sequence: None,
}],
evidence: vec![MemoryEvidenceSpec {
id: "evidence:first".to_string(),
supports: vec!["observation:first".to_string()],
text: "Latency doubled at 10:00.".to_string(),
source: Some("synthetic-fixture".to_string()),
time: Some("2026-08-04T10:00:00Z".to_string()),
metadata: BTreeMap::new(),
}],
provenance: Some(MemoryProvenanceSpec {
source_kind: "projection".to_string(),
source_agent: "contract-test".to_string(),
observed_at: "2026-08-04T10:00:05Z".to_string(),
correlation_id: Some("corr-1".to_string()),
causation_id: None,
}),
idempotency_key: "record:contract-test:1".to_string(),
};
let bytes = serde_json::to_vec(&request).expect("serializes");
assert_eq!(
serde_json::from_slice::<MemoryRecordRequest>(&bytes).expect("deserializes"),
request
);
}
}