use anyhow::Result;
use rusqlite::params;
use crate::memory::edge::{insert_pairwise_conflict_edges, MemoryEdgeWriteContext};
use super::super::{current_state, CurrentStateRequest};
use super::support::{
current_state_test_conn, insert_current_state_memory_at, insert_state_key, set_current_memory,
};
#[test]
fn as_of_lookup_ignores_state_keys_created_after_cutoff() -> Result<()> {
let conn = current_state_test_conn()?;
conn.execute(
"INSERT INTO memory_state_keys
(id, owner_scope, owner_key, memory_type, state_key, state_label,
state_status, current_memory_id, created_at_epoch, updated_at_epoch)
VALUES (10, 'repo', '/repo', 'decision', 'deploy-target',
'deploy target', 'active', NULL, 100, 100)",
[],
)?;
conn.execute(
"INSERT INTO memory_state_keys
(id, owner_scope, owner_key, memory_type, state_key, state_label,
state_status, current_memory_id, created_at_epoch, updated_at_epoch)
VALUES (11, 'user', 'user:default', 'decision', 'deploy-target',
'future global deploy target', 'active', NULL, 300, 300)",
[],
)?;
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Repo deploy target",
"Use staging.",
"active",
10,
100,
Some(100),
None,
)?;
insert_current_state_memory_at(
&conn,
2,
"/repo",
"Future global deploy target",
"Use production.",
"active",
11,
300,
Some(300),
None,
)?;
conn.execute(
"UPDATE memory_state_keys SET current_memory_id = ?1 WHERE id = ?2",
params![1_i64, 10_i64],
)?;
conn.execute(
"UPDATE memory_state_keys SET current_memory_id = ?1 WHERE id = ?2",
params![2_i64, 11_i64],
)?;
let result = current_state(
&conn,
&CurrentStateRequest {
as_of_epoch: Some(200),
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(result.status, "current");
assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(1));
assert!(result.matches.is_empty());
Ok(())
}
#[test]
fn as_of_excludes_open_ended_stale_rows_after_stale_update() -> Result<()> {
let conn = current_state_test_conn()?;
insert_state_key(&conn)?;
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Manual stale deploy target",
"Use staging.",
"stale",
10,
100,
Some(100),
None,
)?;
conn.execute(
"UPDATE memories SET updated_at_epoch = 200 WHERE id = 1",
[],
)?;
set_current_memory(&conn, 1)?;
let before_stale = current_state(
&conn,
&CurrentStateRequest {
as_of_epoch: Some(150),
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
let after_stale = current_state(
&conn,
&CurrentStateRequest {
as_of_epoch: Some(250),
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(before_stale.status, "current");
assert_eq!(
before_stale.current.as_ref().map(|memory| memory.id),
Some(1)
);
assert_eq!(after_stale.status, "no_current");
assert!(after_stale.current.is_none());
Ok(())
}
#[test]
fn explicit_owner_as_of_uses_distinct_owner_key_parameter() -> Result<()> {
let conn = current_state_test_conn()?;
insert_state_key(&conn)?;
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Repo deploy target",
"Use staging.",
"active",
10,
100,
Some(100),
None,
)?;
set_current_memory(&conn, 1)?;
let result = current_state(
&conn,
&CurrentStateRequest {
as_of_epoch: Some(150),
state_key: "deploy-target".to_string(),
owner_scope: Some("repo".to_string()),
owner_key: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(result.status, "current");
assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(1));
Ok(())
}
#[test]
fn as_of_lookup_does_not_return_active_row_updated_after_cutoff() -> Result<()> {
let conn = current_state_test_conn()?;
insert_state_key(&conn)?;
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Initial deploy target",
"Use staging.",
"active",
10,
100,
Some(100),
None,
)?;
conn.execute(
"UPDATE memories
SET title = 'Updated deploy target',
content = 'Use production.',
updated_at_epoch = 300
WHERE id = 1",
[],
)?;
set_current_memory(&conn, 1)?;
let result = current_state(
&conn,
&CurrentStateRequest {
as_of_epoch: Some(150),
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(result.status, "no_current");
assert!(result.current.is_none());
Ok(())
}
#[test]
fn as_of_history_excludes_memory_rows_updated_after_cutoff() -> Result<()> {
let conn = current_state_test_conn()?;
insert_state_key(&conn)?;
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Original deploy target",
"Use staging.",
"stale",
10,
100,
Some(100),
Some(200),
)?;
insert_current_state_memory_at(
&conn,
2,
"/repo",
"Current deploy target",
"Use production.",
"active",
10,
200,
Some(200),
None,
)?;
set_current_memory(&conn, 2)?;
conn.execute(
"INSERT INTO memory_edges
(edge_type, from_memory_id, to_memory_id, state_key_id, reason, created_at_epoch)
VALUES ('supersedes', 1, 2, 10, 'replacement', 210)",
[],
)?;
let before_mutation = current_state(
&conn,
&CurrentStateRequest {
as_of_epoch: Some(250),
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(
before_mutation.current.as_ref().map(|memory| memory.id),
Some(2)
);
assert_eq!(before_mutation.history.len(), 1);
assert_eq!(before_mutation.history[0].id, 1);
assert_eq!(before_mutation.history[0].title, "Original deploy target");
assert_eq!(before_mutation.history[0].status, "stale");
conn.execute(
"UPDATE memories
SET title = 'Future edited deploy target',
status = 'archived',
updated_at_epoch = 300
WHERE id = 1",
[],
)?;
let after_mutation = current_state(
&conn,
&CurrentStateRequest {
as_of_epoch: Some(250),
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(
after_mutation.current.as_ref().map(|memory| memory.id),
Some(2)
);
assert!(after_mutation.history.is_empty());
assert_eq!(after_mutation.why.len(), 1);
Ok(())
}
#[test]
fn current_lookup_respects_memory_validity_window() -> Result<()> {
let conn = current_state_test_conn()?;
insert_state_key(&conn)?;
let now = chrono::Utc::now().timestamp();
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Future deploy target",
"Use production later.",
"active",
10,
now,
Some(now + 100),
None,
)?;
insert_current_state_memory_at(
&conn,
2,
"/repo",
"Expired deploy target",
"Use staging before cutoff.",
"active",
10,
now - 200,
Some(now - 200),
Some(now - 100),
)?;
set_current_memory(&conn, 1)?;
let future_result = current_state(
&conn,
&CurrentStateRequest {
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
set_current_memory(&conn, 2)?;
let expired_result = current_state(
&conn,
&CurrentStateRequest {
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(future_result.status, "no_current");
assert!(future_result.current.is_none());
assert_eq!(expired_result.status, "no_current");
assert!(expired_result.current.is_none());
Ok(())
}
#[test]
fn current_conflicts_ignore_future_active_rivals() -> Result<()> {
let conn = current_state_test_conn()?;
insert_state_key(&conn)?;
let now = chrono::Utc::now().timestamp();
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Deploy target",
"Use production.",
"active",
10,
now - 100,
Some(now - 100),
None,
)?;
insert_current_state_memory_at(
&conn,
2,
"/repo",
"Future rival deploy target",
"Use canary later.",
"active",
10,
now,
Some(now + 100),
None,
)?;
set_current_memory(&conn, 1)?;
conn.execute(
"INSERT INTO memory_edges
(edge_type, from_memory_id, to_memory_id, state_key_id, reason, created_at_epoch)
VALUES ('conflicts', 2, 1, 10, 'future rival', ?1)",
params![now],
)?;
let result = current_state(
&conn,
&CurrentStateRequest {
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(result.status, "current");
assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(1));
assert!(result.conflicts.is_empty());
Ok(())
}
#[test]
fn current_conflict_refs_include_edge_evidence() -> Result<()> {
let conn = current_state_test_conn()?;
insert_state_key(&conn)?;
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Deploy target",
"Use production.",
"active",
10,
100,
Some(100),
None,
)?;
insert_current_state_memory_at(
&conn,
2,
"/repo",
"Conflicting deploy target",
"Use staging.",
"active",
10,
110,
Some(110),
None,
)?;
set_current_memory(&conn, 1)?;
conn.execute(
"INSERT INTO memory_candidates
(id, project_id, scope, memory_type, topic_key, text, evidence_event_ids,
confidence, risk_class, review_status, created_at_epoch, updated_at_epoch)
VALUES (30, NULL, 'project', 'decision', 'deploy-target',
'Use staging conflicts with production.', '[21,22]', 0.8,
'medium', 'approved', 115, 115)",
[],
)?;
conn.execute(
"INSERT INTO memory_operation_log
(id, operation, planner_version, actor, source, owner_scope, owner_key,
memory_type, state_key, source_candidate_id, result_memory_id, created_at_epoch)
VALUES (40, 'conflict', 'test', 'test', 'test', 'repo', '/repo',
'decision', 'deploy-target', 30, 1, 118)",
[],
)?;
conn.execute(
"INSERT INTO memory_edges
(edge_type, from_memory_id, to_memory_id, state_key_id, evidence_event_ids,
source_candidate_id, source_operation_id, reason, created_at_epoch)
VALUES ('conflicts', 2, 1, 10, '[21,22]', 30, 40,
'operator conflict', 120)",
[],
)?;
let result = current_state(
&conn,
&CurrentStateRequest {
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(result.status, "unresolved_conflict");
assert_eq!(result.conflicts.len(), 1);
assert_eq!(result.conflicts[0].id, 2);
assert_eq!(result.conflicts[0].relation.as_deref(), Some("conflicts"));
assert_eq!(
result.conflicts[0].reason.as_deref(),
Some("operator conflict")
);
assert_eq!(result.conflicts[0].evidence_event_ids, vec![21, 22]);
assert_eq!(result.conflicts[0].source_candidate_id, Some(30));
assert_eq!(result.conflicts[0].source_operation_id, Some(40));
Ok(())
}
#[test]
fn cross_state_conflicts_have_per_side_why_scope() -> Result<()> {
let conn = current_state_test_conn()?;
conn.execute(
"INSERT INTO memory_state_keys
(id, owner_scope, owner_key, memory_type, state_key, state_label,
state_status, current_memory_id, created_at_epoch, updated_at_epoch)
VALUES
(10, 'repo', '/repo', 'decision', 'deploy-target',
'deploy target', 'active', NULL, 1, 10),
(11, 'repo', '/repo', 'decision', 'runtime-target',
'runtime target', 'active', NULL, 1, 10)",
[],
)?;
insert_current_state_memory_at(
&conn,
1,
"/repo",
"Deploy target",
"Use production.",
"active",
10,
100,
Some(100),
None,
)?;
insert_current_state_memory_at(
&conn,
2,
"/repo",
"Runtime target",
"Use staging.",
"active",
11,
110,
Some(110),
None,
)?;
conn.execute(
"UPDATE memory_state_keys
SET current_memory_id = CASE id WHEN 10 THEN 1 WHEN 11 THEN 2 END
WHERE id IN (10, 11)",
[],
)?;
let inserted = insert_pairwise_conflict_edges(
&conn,
&[1, 2],
MemoryEdgeWriteContext {
evidence_event_ids: &[7, 8],
reason: Some("cross-state conflict"),
..Default::default()
},
)?;
assert_eq!(inserted, 2);
let deploy_edge_count: i64 = conn.query_row(
"SELECT COUNT(*) FROM memory_edges
WHERE edge_type = 'conflicts' AND state_key_id = 10",
[],
|row| row.get(0),
)?;
let runtime_edge_count: i64 = conn.query_row(
"SELECT COUNT(*) FROM memory_edges
WHERE edge_type = 'conflicts' AND state_key_id = 11",
[],
|row| row.get(0),
)?;
assert_eq!(deploy_edge_count, 1);
assert_eq!(runtime_edge_count, 1);
let deploy = current_state(
&conn,
&CurrentStateRequest {
state_key: "deploy-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(deploy.status, "current");
assert!(deploy.conflicts.is_empty());
assert_eq!(
deploy
.why
.iter()
.filter(|why| why.edge_type == "conflicts")
.count(),
1
);
let runtime = current_state(
&conn,
&CurrentStateRequest {
state_key: "runtime-target".to_string(),
project: Some("/repo".to_string()),
memory_type: Some("decision".to_string()),
include_history: true,
..Default::default()
},
)?;
assert_eq!(runtime.status, "current");
assert!(runtime.conflicts.is_empty());
assert_eq!(
runtime
.why
.iter()
.filter(|why| why.edge_type == "conflicts")
.count(),
1
);
Ok(())
}