- 1
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 2
- 3
//! `EntryPayload::TurnCard` (docs/design/68-context-engine.md §10): written - 4
//! once at turn close, never model-visible raw, and a closed turn's - 5
//! `derive_messages` slice must equal its own `full_record` exactly. - 6
- 7
use std::path::PathBuf; - 8
- 9
use tempfile::tempdir; - 10
- 11
use vak_llm::{ContentBlock, Message, Role}; - 12
use vak_session::types::{ - 13
EntryPayload, FrozenContract, MessageRecord, SessionHeader, TurnCardRecord, - 14
}; - 15
use vak_session::{Fidelity, SessionLog, TurnIndex, WorkingSetPlan}; - 16
- 17
fn header() -> SessionHeader { - 18
SessionHeader { - 19
agent: None, - 20
session_id: "s-test".into(), - 21
created_at: chrono::Utc::now(), - 22
cwd: PathBuf::from("/tmp/proj"), - 23
parent_session_id: None, - 24
contract_id: None, - 25
work_item_id: None, - 26
conversation: None, - 27
contract: FrozenContract { - 28
app_version: "0.1.0".into(), - 29
provider: "anthropic".into(), - 30
model: "claude-sonnet-4-5".into(), - 31
route_ladder: Vec::new(), - 32
route_objective: String::new(), - 33
route_annotations: Vec::new(), - 34
system_prompt: "system prompt v1".into(), - 35
permission_mode: "workspace-write".into(), - 36
capabilities: Vec::new(), - 37
prompt_layers: Vec::new(), - 38
}, - 39
} - 40
} - 41
- 42
fn user_msg(text: &str) -> MessageRecord { - 43
MessageRecord { - 44
message: Message::user_text(text), - 45
meta: None, - 46
} - 47
} - 48
- 49
fn assistant_msg(text: &str) -> MessageRecord { - 50
MessageRecord { - 51
message: Message::assistant(vec![ContentBlock::text(text)]), - 52
meta: None, - 53
} - 54
} - 55
- 56
#[test] - 57
fn a_turn_card_entry_round_trips_through_jsonl() { - 58
let dir = tempdir().unwrap(); - 59
let path = dir.path().join("s.jsonl"); - 60
let mut log = SessionLog::create(path.clone(), header()).unwrap(); - 61
let turn_id = log - 62
.append_message(user_msg("how tall is Everest")) - 63
.unwrap() - 64
.id; - 65
log.append_message(assistant_msg("8,849 meters")).unwrap(); - 66
- 67
let index = TurnIndex::from_log(&log); - 68
let turn = index.turn_by_id(&turn_id).unwrap(); - 69
let card = turn.build_card("completed", "8,849 meters".to_string(), &|s| { - 70
s.len() as u64 / 4 - 71
}); - 72
log.append_turn_card(TurnCardRecord { - 73
turn_id: turn_id.clone(), - 74
card: card.clone(), - 75
}) - 76
.unwrap(); - 77
drop(log); - 78
- 79
let reopened = SessionLog::open(path).unwrap(); - 80
let cards = reopened.turn_cards(); - 81
assert_eq!(cards.len(), 1); - 82
assert_eq!(cards[0].0, turn_id); - 83
assert_eq!(cards[0].1.outcome, "completed"); - 84
assert_eq!(cards[0].1.answered.narration, "8,849 meters"); - 85
} - 86
- 87
#[test] - 88
fn derive_messages_skips_the_turn_card_entry() { - 89
let dir = tempdir().unwrap(); - 90
let mut log = SessionLog::create(dir.path().join("s.jsonl"), header()).unwrap(); - 91
let turn_id = log.append_message(user_msg("hi")).unwrap().id; - 92
log.append_message(assistant_msg("hello")).unwrap(); - 93
let index = TurnIndex::from_log(&log); - 94
let card = - 95
index - 96
.turn_by_id(&turn_id) - 97
.unwrap() - 98
.build_card("completed", "hello".to_string(), &|s| s.len() as u64 / 4); - 99
log.append_turn_card(TurnCardRecord { - 100
turn_id: turn_id.clone(), - 101
card, - 102
}) - 103
.unwrap(); - 104
- 105
let joined: String = log - 106
.derive_messages() - 107
.iter() - 108
.map(|m| m.text_content()) - 109
.collect::<Vec<_>>() - 110
.join("\n"); - 111
assert!( - 112
!joined.contains("outcome") && !joined.contains("tokens_full"), - 113
"the raw card entry must never be model-visible" - 114
); - 115
assert!( - 116
log.chain_to_root() - 117
.iter() - 118
.any(|e| matches!(e.payload, EntryPayload::TurnCard(_))), - 119
"the entry is still on the ledger, just not projected" - 120
); - 121
} - 122
- 123
fn tool_call(id: &str, name: &str) -> MessageRecord { - 124
MessageRecord { - 125
message: Message::assistant(vec![ContentBlock::ToolUse { - 126
id: id.into(), - 127
name: name.into(), - 128
input: serde_json::json!({}), - 129
}]), - 130
meta: None, - 131
} - 132
} - 133
- 134
fn tool_result(id: &str, content: &str) -> MessageRecord { - 135
MessageRecord { - 136
message: Message { - 137
role: Role::User, - 138
content: vec![ContentBlock::tool_result(id, content)], - 139
}, - 140
meta: None, - 141
} - 142
} - 143
- 144
/// No-cut invariant (docs/design/68-context-engine.md, Verification): every - 145
/// tool result recorded in the ledger is either verbatim (the still-open - 146
/// turn), a trace line naming its evidence id (a closed turn in the working - 147
/// set), or covered by a compaction packet — never silently absent. - 148
#[test] - 149
fn every_tool_result_is_verbatim_traced_or_covered_by_a_packet() { - 150
let dir = tempdir().unwrap(); - 151
let mut log = SessionLog::create(dir.path().join("s.jsonl"), header()).unwrap(); - 152
- 153
// Turn A: closed, will be packeted away. - 154
let a = log.append_message(user_msg("turn a")).unwrap().id; - 155
log.append_message(tool_call("ev-a", "search")).unwrap(); - 156
log.append_message(tool_result("ev-a", "result a")).unwrap(); - 157
log.append_message(assistant_msg("a done")).unwrap(); - 158
- 159
// Turn B: closed, stays in the working set as a full record. - 160
let b = log.append_message(user_msg("turn b")).unwrap().id; - 161
log.append_message(tool_call("ev-b", "search")).unwrap(); - 162
log.append_message(tool_result("ev-b", "result b")).unwrap(); - 163
log.append_message(assistant_msg("b done")).unwrap(); - 164
- 165
// Turn C: still open, stays fully verbatim. - 166
log.append_message(user_msg("turn c")).unwrap(); - 167
log.append_message(tool_call("ev-c", "search")).unwrap(); - 168
log.append_message(tool_result("ev-c", "result c")).unwrap(); - 169
- 170
// Packet turn A away; B rides at Full and the open C is verbatim. - 171
log.append_packet(&a, &a, "m", "turn a summarized".into(), 100) - 172
.unwrap(); - 173
let plan = WorkingSetPlan { - 174
per_turn: vec![(b, Fidelity::Full)], - 175
packet_range: Some((a.clone(), a)), - 176
..WorkingSetPlan::default() - 177
}; - 178
- 179
let messages = log.derive_with_plan(&plan); - 180
let joined: String = messages - 181
.iter() - 182
.map(|m| m.text_content()) - 183
.collect::<Vec<_>>() - 184
.join("\n"); - 185
let result_content = |id: &str| -> Option<String> { - 186
messages.iter().find_map(|m| { - 187
m.content.iter().find_map(|b| match b { - 188
ContentBlock::ToolResult { - 189
tool_use_id, - 190
content, - 191
.. - 192
} if tool_use_id == id => Some(content.clone()), - 193
_ => None, - 194
}) - 195
}) - 196
}; - 197
- 198
// ev-a: packeted — covered by the packet summary, never present as a - 199
// block and never digested. - 200
assert!(result_content("ev-a").is_none()); - 201
assert!(!joined.contains("evidence:ev-a")); - 202
assert!(joined.contains("<context_summary>")); - 203
- 204
// ev-b: closed and kept — the pair stays, the result is the digest - 205
// naming the evidence id, not the raw content. - 206
let b = result_content("ev-b").expect("closed turn keeps its pair"); - 207
assert!(b.contains("[evidence:ev-b"), "{b}"); - 208
- 209
// ev-c: still open — verbatim. - 210
assert_eq!(result_content("ev-c").as_deref(), Some("result c")); - 211
} - 212
- 213
#[test] - 214
fn a_closed_turns_derive_messages_slice_equals_its_full_record() { - 215
let dir = tempdir().unwrap(); - 216
let mut log = SessionLog::create(dir.path().join("s.jsonl"), header()).unwrap(); - 217
log.append_message(user_msg("first")).unwrap(); - 218
log.append_message(assistant_msg("first reply")).unwrap(); - 219
log.append_message(user_msg("second")).unwrap(); - 220
log.append_message(assistant_msg("second reply")).unwrap(); - 221
- 222
let index = TurnIndex::from_log(&log); - 223
let first_turn = &index.turns[0]; - 224
assert!(first_turn.closed); - 225
let full_record = first_turn.full_record(); - 226
- 227
let projected = log.derive_messages(); - 228
assert_eq!(&projected[0..2], &full_record[..]); - 229
} - 230
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.