use super::{
LedgerEntry, LifecycleStore, ProposeMemoryRequest, RecordMemoryRequest, TransitionMetadata,
internal::{transition_memory, write_record},
projection::read_projection,
};
use crate::domain::{MemoryLedgerAction, MemoryLifecycleState, MemoryRecord, MemoryScope};
pub fn record_manual_memory(
store: &LifecycleStore,
request: RecordMemoryRequest,
) -> anyhow::Result<LedgerEntry> {
let mut record = MemoryRecord::new_manual(
request.title,
request.summary,
request.memory_type,
request.scope,
request.source_ref,
);
if let Some(project_id) = request.project_id {
record = record.with_project_id(project_id);
}
if let Some(user_id) = request.user_id {
record = record.with_user_id(user_id);
}
if let Some(sensitivity) = request.sensitivity {
record = record.with_sensitivity(sensitivity);
}
record.entities = request.entities;
record.tags = request.tags;
record.triggers = request.triggers;
record.related_files = request.related_files;
record.related_records = request.related_records;
record.supersedes = request.supersedes;
record.applies_to = request.applies_to;
record.valid_until = request.valid_until;
backfill_applies_to(&mut record);
write_record(store, record, request.metadata)
}
pub fn propose_ai_memory(
store: &LifecycleStore,
request: ProposeMemoryRequest,
) -> anyhow::Result<LedgerEntry> {
let mut record = MemoryRecord::new_ai_proposal(
request.title,
request.summary,
request.memory_type,
request.scope,
request.source_ref,
);
if let Some(project_id) = request.project_id {
record = record.with_project_id(project_id);
}
if let Some(user_id) = request.user_id {
record = record.with_user_id(user_id);
}
if let Some(sensitivity) = request.sensitivity {
record = record.with_sensitivity(sensitivity);
}
record.entities = request.entities;
record.tags = request.tags;
record.triggers = request.triggers;
record.related_files = request.related_files;
record.related_records = request.related_records;
record.supersedes = request.supersedes;
record.applies_to = request.applies_to;
record.valid_until = request.valid_until;
backfill_applies_to(&mut record);
write_record(store, record, request.metadata)
}
pub fn accept_memory(store: &LifecycleStore, record_id: &str) -> anyhow::Result<LedgerEntry> {
accept_memory_with_metadata(store, record_id, TransitionMetadata::default())
}
pub fn accept_memory_with_metadata(
store: &LifecycleStore,
record_id: &str,
metadata: TransitionMetadata,
) -> anyhow::Result<LedgerEntry> {
transition_memory(store, record_id, MemoryLedgerAction::Accept, metadata)
}
pub fn promote_memory_to_canonical(
store: &LifecycleStore,
record_id: &str,
) -> anyhow::Result<LedgerEntry> {
promote_memory_to_canonical_with_metadata(store, record_id, TransitionMetadata::default())
}
pub fn promote_memory_to_canonical_with_metadata(
store: &LifecycleStore,
record_id: &str,
metadata: TransitionMetadata,
) -> anyhow::Result<LedgerEntry> {
transition_memory(
store,
record_id,
MemoryLedgerAction::PromoteToCanonical,
metadata,
)
}
pub fn archive_memory(store: &LifecycleStore, record_id: &str) -> anyhow::Result<LedgerEntry> {
archive_memory_with_metadata(store, record_id, TransitionMetadata::default())
}
pub fn archive_memory_with_metadata(
store: &LifecycleStore,
record_id: &str,
metadata: TransitionMetadata,
) -> anyhow::Result<LedgerEntry> {
transition_memory(store, record_id, MemoryLedgerAction::Archive, metadata)
}
pub fn read_events_for_record(
store: &LifecycleStore,
record_id: &str,
) -> anyhow::Result<Vec<LedgerEntry>> {
Ok(store
.read_all()?
.into_iter()
.filter(|entry| entry.record_id == record_id)
.collect())
}
pub fn project_latest_state(
store: &LifecycleStore,
record_id: &str,
) -> anyhow::Result<Option<MemoryRecord>> {
Ok(read_projection(store)?
.latest_by_record_id(record_id)
.map(|entry| entry.record.clone()))
}
pub fn latest_state_entries(store: &LifecycleStore) -> anyhow::Result<Vec<LedgerEntry>> {
Ok(read_projection(store)?.latest_entries().to_vec())
}
pub fn pending_review_entries(store: &LifecycleStore) -> anyhow::Result<Vec<LedgerEntry>> {
Ok(read_projection(store)?.pending_review())
}
pub fn wakeup_ready_entries(store: &LifecycleStore) -> anyhow::Result<Vec<LedgerEntry>> {
Ok(read_projection(store)?.wakeup_ready())
}
pub fn latest_state_by_scope(
store: &LifecycleStore,
scope: MemoryScope,
scope_key: &str,
) -> anyhow::Result<Vec<LedgerEntry>> {
Ok(read_projection(store)?.by_scope(scope, scope_key))
}
pub fn latest_state_by_state(
store: &LifecycleStore,
state: MemoryLifecycleState,
) -> anyhow::Result<Vec<LedgerEntry>> {
Ok(read_projection(store)?.by_state(state))
}
pub fn review_queue_for_scope(
store: &LifecycleStore,
scope: MemoryScope,
scope_key: &str,
) -> anyhow::Result<Vec<LedgerEntry>> {
Ok(read_projection(store)?
.pending_review()
.into_iter()
.filter(|entry| entry.record.scope == scope && entry.scope_key == scope_key)
.collect())
}
pub fn wakeup_ready_for_scope(
store: &LifecycleStore,
scope: MemoryScope,
scope_key: &str,
) -> anyhow::Result<Vec<LedgerEntry>> {
Ok(read_projection(store)?
.wakeup_ready()
.into_iter()
.filter(|entry| entry.record.scope == scope && entry.scope_key == scope_key)
.collect())
}
pub fn lifecycle_query_plan() -> &'static str {
"next query layer should expose latest-state reads by record_id/scope/state plus pending_review and wakeup_ready projections"
}
pub fn review_queue_plan() -> &'static str {
"review queue should read projected latest state and allow optional scope/scope_key filtering"
}
fn backfill_applies_to(record: &mut MemoryRecord) {
if record.scope == MemoryScope::Project
&& let Some(ref pid) = record.project_id
&& !record.applies_to.iter().any(|a| a == pid)
{
record.applies_to.push(pid.clone());
}
}