返回 CodeWhale
journal.rs
根目录 / crates / core / src / journal.rs
1 //! Session tree journal placeholder (issue #5262).
2 //!
3 //! The journal is append-only with an in-memory tree projection:
4 //! every non-header entry carries `id` + `parentId`, the active position is
5 //! a `leafId`, appending creates a child of the leaf, and branching only
6 //! moves the leaf — it never rewrites history. This file lands the entry
7 //! shape that #5262's tree operations hang off of; compaction and
8 //! branch-summary entry kinds are included as first-class kinds but their
9 //! *strategies* are deferred.
10 //!
11 //! Re-exports the protocol journal as the canonical shape so `protocol` and
12 //! `core` agree on the wire. `core` adds the `SessionJournal` wrapper that
13 //! owns the `current_leaf_id` column in `state.threads`.
14
15 pub use codewhale_protocol::journal::{Journal, JournalEntry};
16
17 use serde::{Deserialize, Serialize};
18
19 /// Persisted thread metadata extension for the tree. This is the
20 /// `current_leaf_id` column added to `state.threads`; `None` before the
21 /// first turn, `Some(id)` after. The existing `threads` JSON shape is
22 /// otherwise unchanged (back-compat: old rows read as `None` and the next
23 /// append mints the header leaf).
24 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25 pub struct ThreadLeafState {
26 pub thread_id: String,
27 pub leaf_id: Option<String>,
28 }
29
30 /// First-class journal entry kinds (data shape lands now; strategies later).
31 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
32 pub enum JournalKind {
33 Header,
34 User,
35 Assistant,
36 ToolResult,
37 Compaction,
38 BranchSummary,
39 }
40
41 impl JournalKind {
42 #[must_use]
43 pub fn as_str(self) -> &'static str {
44 match self {
45 Self::Header => "header",
46 Self::User => "user",
47 Self::Assistant => "assistant",
48 Self::ToolResult => "tool_result",
49 Self::Compaction => "compaction",
50 Self::BranchSummary => "branch_summary",
51 }
52 }
53 }
54
55 #[cfg(test)]
56 mod tests {
57 use super::*;
58 use serde_json::json;
59
60 #[test]
61 fn leaf_state_roundtrip() {
62 let s = ThreadLeafState {
63 thread_id: "thread-1".into(),
64 leaf_id: Some("entry-abc".into()),
65 };
66 let j = serde_json::to_string(&s).unwrap();
67 let back: ThreadLeafState = serde_json::from_str(&j).unwrap();
68 assert_eq!(back, s);
69 }
70
71 #[test]
72 fn journal_append_is_child_of_leaf() {
73 let mut j = Journal::new();
74 let a = j.append("header", json!({}));
75 let b = j.append("user", json!("hi"));
76 assert_eq!(j.get(&b).unwrap().parent_id.as_deref(), Some(a.as_str()));
77 }
78 }
79
79 lines RUST