use serde_json::json;
pub const MEMORY_LEVEL_TRANSITION_ACTION: &str = "memory.level_transition";
pub const MEMORY_LEVEL_TRANSITION_AUDIT_SCHEMA_V1: &str = "ee.audit.memory_level_transition.v1";
pub const LEVEL_TRANSITION_TOMBSTONED_REJECTED_CODE: &str = "level_transition_tombstoned_rejected";
pub const LEVEL_TRANSITION_REQUIRES_EVIDENCE_CODE: &str = "level_transition_requires_evidence";
pub const LEVEL_TRANSITION_CONCURRENT_CONFLICT_CODE: &str = "level_transition_concurrent_conflict";
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum MemoryLifecycleState {
Working,
Episodic,
Semantic,
Procedural,
Tombstoned,
}
impl MemoryLifecycleState {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Working => "working",
Self::Episodic => "episodic",
Self::Semantic => "semantic",
Self::Procedural => "procedural",
Self::Tombstoned => "tombstoned",
}
}
#[must_use]
pub const fn all() -> [Self; 5] {
[
Self::Working,
Self::Episodic,
Self::Semantic,
Self::Procedural,
Self::Tombstoned,
]
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MemoryLifecycleTransition {
pub from: MemoryLifecycleState,
pub event: &'static str,
pub to: MemoryLifecycleState,
pub automatic: bool,
pub reason: &'static str,
pub evidence: &'static [&'static str],
}
const WORKFLOW_ID_EVIDENCE: &[&str] = &["workflow_id"];
const MANUAL_EVIDENCE: &[&str] = &["actor", "reason"];
const EPISODIC_CLUSTER_EVIDENCE: &[&str] = &["episodic_memory_ids"];
const CURATION_EVIDENCE: &[&str] = &["curation_candidate_id", "evidence_refs"];
const FEEDBACK_EVIDENCE: &[&str] = &["feedback_event_ids"];
const DECAY_EVIDENCE: &[&str] = &["decay_evaluation"];
const VALID_TO_EVIDENCE: &[&str] = &["valid_to"];
pub const TRANSITIONS: &[MemoryLifecycleTransition] = &[
MemoryLifecycleTransition {
from: MemoryLifecycleState::Working,
event: "workflow.completed",
to: MemoryLifecycleState::Episodic,
automatic: true,
reason: "workflow_close",
evidence: WORKFLOW_ID_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Working,
event: "manual.promote_to_episodic",
to: MemoryLifecycleState::Episodic,
automatic: false,
reason: "manual_promotion",
evidence: MANUAL_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Episodic,
event: "repeated_observation",
to: MemoryLifecycleState::Semantic,
automatic: true,
reason: "clustered_repeated_observation",
evidence: EPISODIC_CLUSTER_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Episodic,
event: "manual.promote_to_semantic",
to: MemoryLifecycleState::Semantic,
automatic: false,
reason: "manual_promotion",
evidence: MANUAL_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Semantic,
event: "curate.apply",
to: MemoryLifecycleState::Procedural,
automatic: true,
reason: "procedural_rule_proposal",
evidence: CURATION_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Semantic,
event: "manual.promote_to_procedural",
to: MemoryLifecycleState::Procedural,
automatic: false,
reason: "manual_promotion",
evidence: MANUAL_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Procedural,
event: "feedback.harmful_decay",
to: MemoryLifecycleState::Semantic,
automatic: true,
reason: "harmful_feedback_decay",
evidence: FEEDBACK_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Procedural,
event: "manual.demote_to_semantic",
to: MemoryLifecycleState::Semantic,
automatic: false,
reason: "manual_demotion",
evidence: MANUAL_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Semantic,
event: "valid_to.set",
to: MemoryLifecycleState::Episodic,
automatic: true,
reason: "time_bound_fact",
evidence: VALID_TO_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Working,
event: "decay.l3",
to: MemoryLifecycleState::Tombstoned,
automatic: true,
reason: "auto_forgetting",
evidence: DECAY_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Episodic,
event: "decay.l3",
to: MemoryLifecycleState::Tombstoned,
automatic: true,
reason: "auto_forgetting",
evidence: DECAY_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Semantic,
event: "decay.l3",
to: MemoryLifecycleState::Tombstoned,
automatic: true,
reason: "auto_forgetting",
evidence: DECAY_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Procedural,
event: "decay.l3",
to: MemoryLifecycleState::Tombstoned,
automatic: true,
reason: "auto_forgetting",
evidence: DECAY_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Working,
event: "manual.tombstone",
to: MemoryLifecycleState::Tombstoned,
automatic: false,
reason: "manual_tombstone",
evidence: MANUAL_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Episodic,
event: "manual.tombstone",
to: MemoryLifecycleState::Tombstoned,
automatic: false,
reason: "manual_tombstone",
evidence: MANUAL_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Semantic,
event: "manual.tombstone",
to: MemoryLifecycleState::Tombstoned,
automatic: false,
reason: "manual_tombstone",
evidence: MANUAL_EVIDENCE,
},
MemoryLifecycleTransition {
from: MemoryLifecycleState::Procedural,
event: "manual.tombstone",
to: MemoryLifecycleState::Tombstoned,
automatic: false,
reason: "manual_tombstone",
evidence: MANUAL_EVIDENCE,
},
];
#[must_use]
pub fn transition_for(
from: MemoryLifecycleState,
event: &str,
) -> Option<&'static MemoryLifecycleTransition> {
TRANSITIONS
.iter()
.find(|transition| transition.from == from && transition.event == event)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MemoryLevelTransitionAudit<'a> {
pub memory_id: &'a str,
pub previous_level: &'a str,
pub new_level: &'a str,
pub reason: &'a str,
pub automatic: bool,
pub event: &'a str,
pub evidence_refs: &'a [&'a str],
pub source_action: Option<&'a str>,
pub previous_trust_class: Option<&'a str>,
pub new_trust_class: Option<&'a str>,
}
#[must_use]
pub fn level_transition_audit_details(input: &MemoryLevelTransitionAudit<'_>) -> String {
let mut payload = json!({
"schema": MEMORY_LEVEL_TRANSITION_AUDIT_SCHEMA_V1,
"memoryId": input.memory_id,
"previousLevel": input.previous_level,
"newLevel": input.new_level,
"reason": input.reason,
"automatic": input.automatic,
"event": input.event,
"evidenceRefs": input.evidence_refs,
"sourceAction": input.source_action,
});
if let (Some(previous), Some(new)) = (input.previous_trust_class, input.new_trust_class) {
payload["previousTrustClass"] = json!(previous);
payload["newTrustClass"] = json!(new);
}
let details_hash = format!(
"blake3:{}",
blake3::hash(payload.to_string().as_bytes()).to_hex()
);
let mut payload_with_hash = payload;
payload_with_hash["detailsHash"] = json!(details_hash);
payload_with_hash.to_string()
}