use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CommitEnvelope {
pub sha: String,
#[serde(skip_serializing_if = "String::is_empty", default)]
pub parent: String,
pub mem: String,
pub timestamp: String,
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
pub trailers: BTreeMap<String, String>,
pub changes: Vec<EntityChange>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "op", rename_all = "lowercase")]
pub enum EntityChange {
Added {
path: String,
content: String,
},
Modified { path: String, content: String },
Deleted { path: String },
Renamed {
from: String,
to: String,
content: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn commit_envelope_json_matches_spec_example() {
let env = CommitEnvelope {
sha: "c4f2a8".to_string(),
parent: "a3f9b1".to_string(),
mem: "engine".to_string(),
timestamp: "2026-05-18T14:23:01Z".to_string(),
trailers: {
let mut m = BTreeMap::new();
m.insert("Tool".to_string(), "memstead_update".to_string());
m.insert("Actor".to_string(), "agent".to_string());
m
},
changes: vec![
EntityChange::Modified {
path: "engine--mem.md".to_string(),
content: "body".to_string(),
},
EntityChange::Deleted {
path: "engine--alt.md".to_string(),
},
],
};
let json = serde_json::to_value(&env).unwrap();
assert_eq!(json["sha"], "c4f2a8");
assert_eq!(json["parent"], "a3f9b1");
assert_eq!(json["mem"], "engine");
assert_eq!(json["timestamp"], "2026-05-18T14:23:01Z");
assert_eq!(json["trailers"]["Tool"], "memstead_update");
assert_eq!(json["changes"][0]["op"], "modified");
assert_eq!(json["changes"][0]["path"], "engine--mem.md");
assert_eq!(json["changes"][1]["op"], "deleted");
}
#[test]
fn entity_change_renamed_serialises_with_from_to() {
let c = EntityChange::Renamed {
from: "engine--x.md".to_string(),
to: "engine--z.md".to_string(),
content: "body".to_string(),
};
let json = serde_json::to_value(&c).unwrap();
assert_eq!(json["op"], "renamed");
assert_eq!(json["from"], "engine--x.md");
assert_eq!(json["to"], "engine--z.md");
assert_eq!(json["content"], "body");
}
}