use serde::{Deserialize, Serialize};
use crate::attribute::AttributeValue;
use crate::graph::NodeRef;
use crate::link::LinkKind;
use crate::memory::{MemoryKind, MemoryRef};
use crate::summary::SummaryRef;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum MemoryAttributeOp {
Set {
mref: MemoryRef,
key: String,
value: AttributeValue,
},
Clear {
mref: MemoryRef,
key: String,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum SummaryAttributeOp {
Set {
sref: SummaryRef,
key: String,
value: AttributeValue,
},
Clear {
sref: SummaryRef,
key: String,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EvolutionOps {
pub trigger: Option<MemoryRef>,
pub note: Option<String>,
pub memory_attributes: Vec<MemoryAttributeOp>,
pub summary_attributes: Vec<SummaryAttributeOp>,
pub links_added: Vec<(NodeRef, NodeRef, LinkKind)>,
pub links_removed: Vec<(NodeRef, NodeRef, LinkKind)>,
pub validity_updates: Vec<(MemoryRef, Option<i64>, Option<i64>)>,
pub kind_updates: Vec<(MemoryRef, MemoryKind)>,
}
impl EvolutionOps {
#[must_use]
pub fn triggered_by(trigger: MemoryRef) -> Self {
EvolutionOps {
trigger: Some(trigger),
..Self::default()
}
}
#[must_use]
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
#[must_use]
pub fn set_memory_attribute(
mut self,
mref: MemoryRef,
key: impl Into<String>,
value: impl Into<AttributeValue>,
) -> Self {
self.memory_attributes.push(MemoryAttributeOp::Set {
mref,
key: key.into(),
value: value.into(),
});
self
}
#[must_use]
pub fn clear_memory_attribute(mut self, mref: MemoryRef, key: impl Into<String>) -> Self {
self.memory_attributes.push(MemoryAttributeOp::Clear {
mref,
key: key.into(),
});
self
}
#[must_use]
pub fn set_summary_attribute(
mut self,
sref: SummaryRef,
key: impl Into<String>,
value: impl Into<AttributeValue>,
) -> Self {
self.summary_attributes.push(SummaryAttributeOp::Set {
sref,
key: key.into(),
value: value.into(),
});
self
}
#[must_use]
pub fn add_link(mut self, src: NodeRef, dst: NodeRef, kind: LinkKind) -> Self {
self.links_added.push((src, dst, kind));
self
}
#[must_use]
pub fn remove_link(mut self, src: NodeRef, dst: NodeRef, kind: LinkKind) -> Self {
self.links_removed.push((src, dst, kind));
self
}
#[must_use]
pub fn close_validity(
mut self,
mref: MemoryRef,
from_ms: Option<i64>,
until_ms: Option<i64>,
) -> Self {
self.validity_updates.push((mref, from_ms, until_ms));
self
}
#[must_use]
pub fn change_kind(mut self, mref: MemoryRef, kind: MemoryKind) -> Self {
self.kind_updates.push((mref, kind));
self
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.memory_attributes.is_empty()
&& self.summary_attributes.is_empty()
&& self.links_added.is_empty()
&& self.links_removed.is_empty()
&& self.validity_updates.is_empty()
&& self.kind_updates.is_empty()
}
#[must_use]
pub fn op_count(&self) -> u64 {
let mut n: u64 = 0;
n += self.memory_attributes.len() as u64;
n += self.summary_attributes.len() as u64;
n += self.links_added.len() as u64;
n += self.links_removed.len() as u64;
n += self.validity_updates.len() as u64;
n += self.kind_updates.len() as u64;
n
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EvolutionApplied {
pub applied: u64,
pub audit_seq: i64,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EvolutionReport {
pub applied: u64,
pub audit_seq: i64,
pub events_emitted: u32,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory::MemoryId;
use crate::partition::PartitionPath;
fn mref() -> MemoryRef {
MemoryRef {
id: MemoryId::generate(),
partition: "p".parse::<PartitionPath>().unwrap(),
}
}
#[test]
fn default_is_empty() {
let ops = EvolutionOps::default();
assert!(ops.is_empty());
assert_eq!(ops.op_count(), 0);
}
#[test]
fn builders_accumulate() {
let r = mref();
let ops = EvolutionOps::triggered_by(r.clone())
.with_note("agent decided")
.set_memory_attribute(r.clone(), "k", "v")
.clear_memory_attribute(r.clone(), "old")
.close_validity(r.clone(), Some(1), Some(2))
.change_kind(r, MemoryKind::Semantic);
assert_eq!(ops.op_count(), 4);
assert_eq!(ops.note.as_deref(), Some("agent decided"));
}
}