use anyhow::{Context, Result};
use crate::memory_candidate::review::approve_candidate;
use super::{
insert_source_observation, process_with_generator, setup_conn, setup_task,
MemoryCandidateResult,
};
const PREFERENCE: &str = "Use bun, not npm, for package installation commands in this project.";
#[tokio::test]
async fn approved_duplicate_preferences_increment_canonical_reinforcement() -> Result<()> {
let mut conn = setup_conn();
for round in 1..=3 {
let task = setup_task(&mut conn, &format!("sess-preference-reinforce-{round}"))?;
insert_source_observation(&conn, &task, PREFERENCE)?;
let result = process_with_generator(&mut conn, &task, |_prompt| async {
Ok(format!(
"<memory_candidate>\
<scope>project</scope>\
<type>preference</type>\
<topic_key>package-manager-choice</topic_key>\
<risk_class>low</risk_class>\
<confidence>0.95</confidence>\
<text>{PREFERENCE}</text>\
</memory_candidate>"
))
})
.await?;
assert_eq!(
result,
MemoryCandidateResult::Written {
candidates: 1,
promoted: 0,
pending_review: 1,
to_event_id: task
.high_watermark_event_id
.context("preference task watermark")?,
}
);
let candidate_id: i64 = conn.query_row(
"SELECT id FROM memory_candidates ORDER BY id DESC LIMIT 1",
[],
|row| row.get(0),
)?;
approve_candidate(&mut conn, candidate_id)?
.context("approved preference should resolve to a memory")?;
}
let memory_count: i64 = conn.query_row(
"SELECT COUNT(*) FROM memories WHERE memory_type = 'preference'",
[],
|row| row.get(0),
)?;
assert_eq!(memory_count, 1);
let (count, machine_checkable, risk_class): (i64, i64, String) = conn.query_row(
"SELECT reinforcement_count, machine_checkable, risk_class
FROM memory_preference_reinforcements",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)?;
assert_eq!(count, 3);
assert_eq!(machine_checkable, 1);
assert_eq!(risk_class, "low");
Ok(())
}
#[tokio::test]
async fn same_evidence_candidates_count_as_one_reinforcement() -> Result<()> {
let mut conn = setup_conn();
let task = setup_task(&mut conn, "sess-preference-same-evidence")?;
insert_source_observation(&conn, &task, PREFERENCE)?;
let result = process_with_generator(&mut conn, &task, |_prompt| async {
Ok((1..=3)
.map(|index| {
format!(
"<memory_candidate>\
<scope>project</scope>\
<type>preference</type>\
<topic_key>package-manager-same-evidence-{index}</topic_key>\
<risk_class>low</risk_class>\
<confidence>0.95</confidence>\
<text>{PREFERENCE}</text>\
</memory_candidate>"
)
})
.collect::<String>())
})
.await?;
assert_eq!(
result,
MemoryCandidateResult::Written {
candidates: 3,
promoted: 0,
pending_review: 3,
to_event_id: task
.high_watermark_event_id
.context("preference task watermark")?,
}
);
let candidate_ids = conn
.prepare("SELECT id FROM memory_candidates ORDER BY id")?
.query_map([], |row| row.get::<_, i64>(0))?
.collect::<std::result::Result<Vec<_>, _>>()?;
for candidate_id in candidate_ids {
approve_candidate(&mut conn, candidate_id)?
.context("approved preference should resolve to a memory")?;
}
let (count, source_evidence): (i64, Option<String>) = conn.query_row(
"SELECT reinforcement_count, source_evidence
FROM memory_preference_reinforcements",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
assert_eq!(count, 1, "one evidence set must count only once");
assert_eq!(
source_evidence,
Some(serde_json::to_string(&vec![task
.high_watermark_event_id
.context("preference task watermark")?])?)
);
Ok(())
}