update: entry hash chain と session_head mutex を撤廃

- HashedEntry / EntryHash / compute_hash / build_chain 撤去、JSONL は 1 行 1 LogEntry
- SessionOrigin.at_hash → at_turn_index (TurnEnd 由来) に置換
- Pod 側 SessionHead mutex を ArcSwap<SessionId> + AtomicUsize の SessionState に置換
- ensure_head_or_fork は store の entry count と writer の append tally で判定
- session-store から sha2 / hex 依存、pod から parking_lot 依存を削除
This commit is contained in:
2026-05-20 04:31:37 +09:00
parent 3d091acacd
commit 90e83bf2ae
17 changed files with 339 additions and 653 deletions
+39 -54
View File
@@ -1,8 +1,6 @@
use llm_worker::WorkerResult;
use llm_worker::llm_client::types::{Item, RequestConfig};
use session_store::{
FsStore, LogEntry, Store, TraceEntry, build_chain, collect_state, new_session_id,
};
use session_store::{FsStore, LogEntry, Store, TraceEntry, collect_state, new_session_id};
#[test]
fn round_trip_write_and_read() {
@@ -10,7 +8,7 @@ fn round_trip_write_and_read() {
let store = FsStore::new(dir.path()).unwrap();
let id = new_session_id();
let raw = vec![
let entries = vec![
LogEntry::SessionStart {
ts: 1000,
system_prompt: Some("You are helpful.".into()),
@@ -37,31 +35,21 @@ fn round_trip_write_and_read() {
result: WorkerResult::Finished,
},
];
let entries = build_chain(&raw);
// Write entries one by one
for entry in &entries {
store.append(id, entry).unwrap();
}
// Read back
let read_back = store.read_all(id).unwrap();
assert_eq!(read_back.len(), entries.len());
// Verify hashes survived round-trip
for (orig, read) in entries.iter().zip(read_back.iter()) {
assert_eq!(orig.hash, read.hash);
assert_eq!(orig.prev_hash, read.prev_hash);
}
// Replay and verify state
let state = collect_state(&read_back);
assert_eq!(state.system_prompt.as_deref(), Some("You are helpful."));
assert_eq!(state.config.max_tokens, Some(1024));
assert_eq!(state.history.len(), 2);
assert_eq!(state.turn_count, 1);
assert!(!state.last_run_interrupted);
assert!(state.head_hash.is_some());
assert_eq!(state.entries_count, entries.len());
}
#[test]
@@ -70,7 +58,7 @@ fn create_session_writes_all_entries() {
let store = FsStore::new(dir.path()).unwrap();
let id = new_session_id();
let entries = build_chain(&[LogEntry::SessionStart {
let entries = [LogEntry::SessionStart {
ts: 1000,
system_prompt: None,
config: RequestConfig::default(),
@@ -80,7 +68,7 @@ fn create_session_writes_all_entries() {
],
forked_from: None,
compacted_from: None,
}]);
}];
store.create_session(id, &entries).unwrap();
let read_back = store.read_all(id).unwrap();
@@ -100,25 +88,17 @@ fn list_sessions_returns_newest_first() {
std::thread::sleep(std::time::Duration::from_millis(2));
let id2 = new_session_id();
let entries1 = build_chain(&[LogEntry::SessionStart {
let entry = LogEntry::SessionStart {
ts: 1000,
system_prompt: None,
config: RequestConfig::default(),
history: vec![],
forked_from: None,
compacted_from: None,
}]);
let entries2 = build_chain(&[LogEntry::SessionStart {
ts: 1001,
system_prompt: None,
config: RequestConfig::default(),
history: vec![],
forked_from: None,
compacted_from: None,
}]);
};
store.append(id1, &entries1[0]).unwrap();
store.append(id2, &entries2[0]).unwrap();
store.append(id1, &entry).unwrap();
store.append(id2, &entry).unwrap();
let sessions = store.list_sessions().unwrap();
assert_eq!(sessions.len(), 2);
@@ -134,15 +114,19 @@ fn exists_returns_correct_state() {
assert!(!store.exists(id).unwrap());
let entries = build_chain(&[LogEntry::SessionStart {
ts: 1000,
system_prompt: None,
config: RequestConfig::default(),
history: vec![],
forked_from: None,
compacted_from: None,
}]);
store.append(id, &entries[0]).unwrap();
store
.append(
id,
&LogEntry::SessionStart {
ts: 1000,
system_prompt: None,
config: RequestConfig::default(),
history: vec![],
forked_from: None,
compacted_from: None,
},
)
.unwrap();
assert!(store.exists(id).unwrap());
}
@@ -163,18 +147,20 @@ fn trace_entries_in_separate_file() {
let store = FsStore::new(dir.path()).unwrap();
let id = new_session_id();
// Write a log entry
let entries = build_chain(&[LogEntry::SessionStart {
ts: 1000,
system_prompt: None,
config: RequestConfig::default(),
history: vec![],
forked_from: None,
compacted_from: None,
}]);
store.append(id, &entries[0]).unwrap();
store
.append(
id,
&LogEntry::SessionStart {
ts: 1000,
system_prompt: None,
config: RequestConfig::default(),
history: vec![],
forked_from: None,
compacted_from: None,
},
)
.unwrap();
// Write a trace entry
let trace = TraceEntry {
ts: 1500,
turn: 0,
@@ -194,12 +180,12 @@ fn trace_entries_in_separate_file() {
}
#[test]
fn read_head_hash_returns_last_entry_hash() {
fn read_entry_count_matches_append_tally() {
let dir = tempfile::tempdir().unwrap();
let store = FsStore::new(dir.path()).unwrap();
let id = new_session_id();
let entries = build_chain(&[
let entries = [
LogEntry::SessionStart {
ts: 1000,
system_prompt: None,
@@ -212,12 +198,11 @@ fn read_head_hash_returns_last_entry_hash() {
ts: 2000,
segments: vec![protocol::Segment::text("Hello")],
},
]);
];
for entry in &entries {
store.append(id, entry).unwrap();
}
let head = store.read_head_hash(id).unwrap();
assert_eq!(head.as_ref(), Some(&entries[1].hash));
assert_eq!(store.read_entry_count(id).unwrap(), entries.len());
}