#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
use meerkat_core::session_store::{SessionHead, TranscriptStrandId, append_only_save_guard};
use meerkat_core::types::{
AssistantBlock, BlockAssistantMessage, Message, StopReason, UserMessage,
};
use meerkat_core::{Session, session_content_digest_bytes, session_content_digest_computations};
const SMALL: usize = 8;
const LARGE: usize = 2_000;
fn user(text: &str) -> Message {
Message::User(UserMessage::text(text))
}
fn assistant(text: &str) -> Message {
Message::BlockAssistant(BlockAssistantMessage::new(
vec![AssistantBlock::Text {
text: text.to_string(),
meta: None,
}],
StopReason::EndTurn,
))
}
fn session_with_turns(turns: usize) -> Session {
let mut session = Session::new();
session.append_system_message("system".to_string());
for index in 0..turns {
session.push(user(&format!(
"question {index} with some body text to make the message non-trivial"
)));
session.push(assistant(&format!(
"answer {index} with some body text to make the message non-trivial"
)));
}
session
}
fn strand() -> TranscriptStrandId {
TranscriptStrandId::root()
}
fn durable_history_value(session: &Session) -> serde_json::Value {
serde_json::to_value(session).expect("session serializes")["metadata"]
[meerkat_core::session::SESSION_TRANSCRIPT_HISTORY_STATE_KEY]
.clone()
}
fn boundary_save_digest_count(turns: usize) -> u64 {
let mut live = session_with_turns(turns);
let mut previous = live.clone();
for warmup in 0..2 {
append_only_save_guard(&live, Some(&previous)).expect("warm-up guard");
SessionHead::from_session(&live, strand(), 0).expect("warm-up head");
previous = live.clone();
live.push(user(&format!("warm-up {warmup}")));
live.push(assistant("warm-up reply"));
}
let before = session_content_digest_computations();
append_only_save_guard(&live, Some(&previous)).expect("boundary guard");
SessionHead::from_session(&live, strand(), 0).expect("boundary head");
session_content_digest_computations() - before
}
#[test]
fn turn_boundary_digest_count_is_independent_of_transcript_size() {
let small = boundary_save_digest_count(SMALL);
let large = boundary_save_digest_count(LARGE);
println!(
"boundary-save full-document digest passes: {SMALL} turns => {small}, {LARGE} turns => {large}"
);
assert_eq!(
small, large,
"turn-boundary digest work must not depend on transcript size \
({SMALL} turns => {small} passes, {LARGE} turns => {large} passes)"
);
}
#[test]
fn steady_state_turn_boundary_spends_no_full_document_digest() {
assert_eq!(
boundary_save_digest_count(LARGE),
0,
"a warm boundary save must serve every transcript digest from the \
incremental accumulator"
);
}
#[test]
fn append_digest_count_is_independent_of_transcript_size() {
fn measure(turns: usize) -> u64 {
let mut live = session_with_turns(turns);
let seeded = live
.transcript_content_digest()
.expect("seed transcript digest");
assert!(seeded.starts_with("sha256:"));
let before = session_content_digest_computations();
live.push(user("one word"));
let after_append = live
.transcript_content_digest()
.expect("append transcript digest");
assert_ne!(seeded, after_append);
session_content_digest_computations() - before
}
let small = measure(SMALL);
let large = measure(LARGE);
println!(
"append full-document digest passes: {SMALL} turns => {small}, {LARGE} turns => {large}"
);
assert_eq!(small, large);
assert_eq!(large, 0, "an append must not re-hash the whole transcript");
}
#[test]
fn ordered_system_message_append_digest_budget_is_constant() {
fn measure(turns: usize) -> u64 {
let mut previous = session_with_turns(turns);
previous.push(user("before a later system message"));
previous.append_system_message("later system message".to_string());
previous
.transcript_content_digest()
.expect("seed the persisted system-message boundary");
let mut live = previous.clone();
live.append_system_message("another later system message".to_string());
live.push(user("after the later system message"));
let before = session_content_digest_computations();
append_only_save_guard(&live, Some(&previous)).expect("ordered system append guard");
session_content_digest_computations() - before
}
let small = measure(SMALL);
let large = measure(LARGE);
println!(
"ordered system-message append digest passes: {SMALL} turns => {small}, {LARGE} turns => {large}"
);
assert_eq!(
small, large,
"ordinary ordered System appends must keep a transcript-size-independent digest budget"
);
assert_eq!(large, 0, "a System append must not re-hash prior rows");
}
#[test]
fn synthetic_notice_refresh_branch_digest_budget_is_constant() {
use meerkat_core::types::{SystemNoticeBlock, SystemNoticeKind, SystemNoticeMessage};
fn notice(server: &str) -> Message {
Message::SystemNotice(SystemNoticeMessage::with_block(
SystemNoticeKind::McpPending,
None,
SystemNoticeBlock::Mcp {
server_id: Some(server.to_string()),
operation: None,
phase: None,
persisted: false,
detail: None,
pending_sources: Vec::new(),
},
))
}
fn measure(turns: usize) -> u64 {
let mut previous = session_with_turns(turns);
previous.push(notice("mcp pending"));
let mut live = previous.clone();
live.replace_synthetic_notices(SystemNoticeKind::McpPending, vec![notice("mcp ready")])
.expect("synthetic notice refresh");
let before = session_content_digest_computations();
let _ = append_only_save_guard(&live, Some(&previous));
session_content_digest_computations() - before
}
let small = measure(SMALL);
let large = measure(LARGE);
println!(
"synthetic-notice-refresh branch digest passes: {SMALL} turns => {small}, {LARGE} turns => {large}"
);
assert_eq!(
small, large,
"the synthetic-notice-refresh acceptance branch must keep a constant digest budget"
);
}
#[test]
fn history_bearing_boundary_save_hashes_zero_content_bytes() {
use meerkat_core::service::{TranscriptRewriteReason, TranscriptRewriteSelection};
fn measure(turns: usize) -> (u64, u64) {
let mut live = session_with_turns(turns);
let end = live.messages().len();
live.commit_transcript_rewrite(
TranscriptRewriteSelection::MessageRange {
start: end - 1,
end,
},
vec![assistant("audited replacement")],
TranscriptRewriteReason::new("unit-test"),
Some("unit-test".to_string()),
None,
)
.expect("audited rewrite");
let audited_graph = durable_history_value(&live);
let mut previous = live.clone();
for warmup in 0..2 {
append_only_save_guard(&live, Some(&previous)).expect("warm-up guard");
SessionHead::from_session(&live, strand(), 1).expect("warm-up head");
previous = live.clone();
live.push(user(&format!("warm-up {warmup}")));
live.push(assistant("warm-up reply"));
}
let passes_before = session_content_digest_computations();
let bytes_before = session_content_digest_bytes();
append_only_save_guard(&live, Some(&previous)).expect("boundary guard");
SessionHead::from_session(&live, strand(), 1).expect("boundary head");
let passes = session_content_digest_computations() - passes_before;
let bytes = session_content_digest_bytes() - bytes_before;
assert_eq!(
durable_history_value(&live),
audited_graph,
"ordinary appends must leave audited graph bytes untouched at the durable wire boundary"
);
(passes, bytes)
}
let (small_passes, small_bytes) = measure(SMALL);
let (large_passes, large_bytes) = measure(LARGE);
println!(
"history-bearing boundary save: {SMALL} turns => {small_passes} passes / {small_bytes} bytes, \
{LARGE} turns => {large_passes} passes / {large_bytes} bytes"
);
assert_eq!(
(small_passes, small_bytes),
(0, 0),
"a warm small history-bearing boundary must not re-hash live or audited history"
);
assert_eq!(
(large_passes, large_bytes),
(0, 0),
"a warm large history-bearing boundary must not re-hash live or audited history"
);
}
#[test]
fn steady_state_turn_boundary_hashes_zero_content_bytes() {
fn boundary_bytes(turns: usize) -> u64 {
let mut live = session_with_turns(turns);
let mut previous = live.clone();
for warmup in 0..2 {
append_only_save_guard(&live, Some(&previous)).expect("warm-up guard");
SessionHead::from_session(&live, strand(), 0).expect("warm-up head");
previous = live.clone();
live.push(user(&format!("warm-up {warmup}")));
live.push(assistant("warm-up reply"));
}
let before = session_content_digest_bytes();
append_only_save_guard(&live, Some(&previous)).expect("boundary guard");
SessionHead::from_session(&live, strand(), 0).expect("boundary head");
session_content_digest_bytes() - before
}
assert_eq!(
boundary_bytes(LARGE),
0,
"a warm plain boundary save must hash zero content bytes"
);
}
#[test]
#[ignore = "manual release-timing evidence"]
fn history_bearing_boundary_release_timing() {
use meerkat_core::service::{TranscriptRewriteReason, TranscriptRewriteSelection};
fn median_boundary(turns: usize) -> std::time::Duration {
let mut live = session_with_turns(turns);
let end = live.messages().len();
let _rewrite = live
.commit_transcript_rewrite(
TranscriptRewriteSelection::MessageRange {
start: end - 1,
end,
},
vec![assistant("audited replacement")],
TranscriptRewriteReason::new("timing"),
Some("timing".to_string()),
None,
)
.expect("audited rewrite");
let mut previous = live.clone();
let mut samples = Vec::new();
for round in 0..45 {
append_only_save_guard(&live, Some(&previous)).expect("warm guard");
SessionHead::from_session(&live, strand(), 1).expect("warm head");
previous = live.clone();
live.push(user(&format!("turn {round}")));
live.push(assistant("reply"));
let start = std::time::Instant::now();
append_only_save_guard(&live, Some(&previous)).expect("boundary guard");
SessionHead::from_session(&live, strand(), 1).expect("boundary head");
samples.push(start.elapsed());
}
let mut steady = samples.split_off(samples.len() - 9);
steady.sort();
steady[steady.len() / 2]
}
let small = median_boundary(SMALL);
let large = median_boundary(LARGE);
println!(
"warm history-bearing boundary median: {SMALL} turns => {small:?}, {LARGE} turns => {large:?} \
(ratio {:.1}x)",
large.as_secs_f64() / small.as_secs_f64().max(f64::EPSILON)
);
}
#[test]
fn history_bearing_append_digest_count_is_independent_of_transcript_size() {
use meerkat_core::service::{TranscriptRewriteReason, TranscriptRewriteSelection};
fn measure(turns: usize) -> u64 {
let mut live = session_with_turns(turns);
let end = live.messages().len();
let rewrite = live
.commit_transcript_rewrite(
TranscriptRewriteSelection::MessageRange {
start: end - 1,
end,
},
vec![assistant("audited replacement")],
TranscriptRewriteReason::new("unit-test"),
Some("unit-test".to_string()),
None,
)
.expect("audited rewrite");
let audited_graph = durable_history_value(&live);
assert!(
live.transcript_history_state()
.expect("history state decodes")
.is_some(),
"fixture must carry a retained transcript graph"
);
for warmup in 0..3 {
live.push(user(&format!("warm-up {warmup}")));
}
let before = session_content_digest_computations();
live.push(user("one word"));
let after = session_content_digest_computations() - before;
let state = live
.transcript_history_state()
.expect("history state decodes")
.expect("history state present");
assert_eq!(
state.head(),
rewrite.revision,
"graph head must remain the latest audited rewrite endpoint"
);
assert_ne!(
state.head(),
live.transcript_content_digest().expect("live digest"),
"ordinary append must advance live identity without manufacturing a graph head"
);
assert_eq!(
durable_history_value(&live),
audited_graph,
"ordinary append must leave graph bytes untouched at the durable wire boundary"
);
live.validate_transcript_history_state()
.expect("graph must still validate after the fast-path appends");
after
}
let small = measure(SMALL);
let large = measure(LARGE);
println!(
"history-bearing append full-document digest passes: {SMALL} turns => {small}, {LARGE} turns => {large}"
);
assert_eq!(
small, large,
"appending to a history-bearing session must not scale with the retained graph"
);
}