claw_branch/types.rs
1//! Shared domain types used across the branch engine.
2
3use std::path::PathBuf;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use uuid::Uuid;
9
10/// Represents a branch in the ClawDB branching graph.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct Branch {
13 /// The unique branch identifier.
14 pub id: Uuid,
15 /// The human-readable branch name.
16 pub name: String,
17 /// The URL-safe branch slug.
18 pub slug: String,
19 /// The parent branch identifier when this branch is forked.
20 pub parent_id: Option<Uuid>,
21 /// The current branch lifecycle status.
22 pub status: BranchStatus,
23 /// The isolated SQLite database path for this branch.
24 pub db_path: PathBuf,
25 /// The snapshot path backing this branch.
26 pub snapshot_path: PathBuf,
27 /// The workspace identifier that owns the branch.
28 pub workspace_id: Uuid,
29 /// The timestamp at which the branch was created.
30 pub created_at: DateTime<Utc>,
31 /// The timestamp at which the branch was last updated.
32 pub updated_at: DateTime<Utc>,
33 /// The source cursor or data version captured when the branch was forked.
34 pub forked_from_cursor: Option<String>,
35 /// An optional branch description.
36 pub description: Option<String>,
37 /// Arbitrary structured metadata associated with the branch.
38 pub metadata: Value,
39 /// Aggregated operational metrics for the branch.
40 pub metrics: BranchMetrics,
41}
42
43/// Describes the current lifecycle status of a branch.
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45pub enum BranchStatus {
46 /// The branch is active and can accept writes.
47 Active,
48 /// The branch is preserved but inactive.
49 Dormant,
50 /// The branch has been merged into another branch.
51 Merged {
52 /// The branch the current branch merged into.
53 merged_into: Uuid,
54 /// The timestamp when the merge completed.
55 merged_at: DateTime<Utc>,
56 },
57 /// The branch has been discarded and awaits cleanup.
58 Discarded {
59 /// The timestamp when the discard happened.
60 discarded_at: DateTime<Utc>,
61 },
62 /// The branch has been archived for later recovery.
63 Archived,
64 /// The branch is detached from lineage and eligible for cleanup.
65 Orphan,
66 /// The branch data has been purged from disk and registry lifecycle.
67 Purged,
68}
69
70impl BranchStatus {
71 /// Returns true when the branch is still live and eligible for activation-related operations.
72 pub fn is_live(&self) -> bool {
73 matches!(self, Self::Active | Self::Dormant)
74 }
75
76 /// Returns true when the branch can accept commits.
77 pub fn can_commit(&self) -> bool {
78 matches!(self, Self::Active)
79 }
80
81 /// Returns the coarse-grained lifecycle kind.
82 pub fn kind(&self) -> &'static str {
83 match self {
84 Self::Active => "active",
85 Self::Dormant => "dormant",
86 Self::Merged { .. } => "merged",
87 Self::Discarded { .. } => "discarded",
88 Self::Archived => "archived",
89 Self::Orphan => "orphan",
90 Self::Purged => "purged",
91 }
92 }
93
94 /// Converts the status to a storage-friendly string.
95 pub fn to_storage(&self) -> String {
96 match self {
97 Self::Active => "active".to_string(),
98 Self::Dormant => "dormant".to_string(),
99 Self::Archived => "archived".to_string(),
100 Self::Orphan => "orphan".to_string(),
101 Self::Purged => "purged".to_string(),
102 Self::Merged {
103 merged_into,
104 merged_at,
105 } => format!("merged:{merged_into}:{merged_at}"),
106 Self::Discarded { discarded_at } => format!("discarded:{discarded_at}"),
107 }
108 }
109
110 /// Parses a storage string back into a branch status.
111 pub fn from_storage(value: &str) -> Option<Self> {
112 match value {
113 "active" => Some(Self::Active),
114 "dormant" => Some(Self::Dormant),
115 "archived" => Some(Self::Archived),
116 "orphan" => Some(Self::Orphan),
117 "purged" => Some(Self::Purged),
118 _ if value.starts_with("merged:") => {
119 let mut parts = value.splitn(3, ':');
120 let _ = parts.next();
121 let merged_into = parts.next().and_then(|part| Uuid::parse_str(part).ok())?;
122 let merged_at = parts
123 .next()
124 .and_then(|part| part.parse::<DateTime<Utc>>().ok())?;
125 Some(Self::Merged {
126 merged_into,
127 merged_at,
128 })
129 }
130 _ if value.starts_with("discarded:") => {
131 let discarded_at = value
132 .split_once(':')
133 .and_then(|(_, timestamp)| timestamp.parse::<DateTime<Utc>>().ok())?;
134 Some(Self::Discarded { discarded_at })
135 }
136 _ => None,
137 }
138 }
139}
140
141/// Captures aggregate metrics for a branch.
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
143pub struct BranchMetrics {
144 /// The number of operations executed on the branch.
145 pub op_count: u64,
146 /// The number of memory records on the branch.
147 pub memory_record_count: i64,
148 /// The number of sessions on the branch.
149 pub session_count: i64,
150 /// The number of tool outputs on the branch.
151 pub tool_output_count: i64,
152 /// The total branch size on disk in bytes.
153 pub bytes_on_disk: u64,
154 /// The computed divergence score relative to trunk or a merge base.
155 pub divergence_score: f64,
156 /// The number of created entities on the branch.
157 pub created_entity_count: u64,
158 /// The number of updated entities on the branch.
159 pub updated_entity_count: u64,
160 /// The number of deleted entities on the branch.
161 pub deleted_entity_count: u64,
162 /// The timestamp of the last recorded branch activity.
163 pub last_activity_at: Option<DateTime<Utc>>,
164}
165
166/// Captures the result of comparing two branches.
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
168pub struct DiffResult {
169 /// The first branch identifier in the comparison.
170 pub branch_a_id: Uuid,
171 /// The second branch identifier in the comparison.
172 pub branch_b_id: Uuid,
173 /// The timestamp when the comparison ran.
174 pub compared_at: DateTime<Utc>,
175 /// Per-entity diffs discovered during comparison.
176 pub entity_diffs: Vec<EntityDiff>,
177 /// Aggregate diff statistics.
178 pub stats: DiffStats,
179 /// The computed divergence score.
180 pub divergence_score: f64,
181}
182
183/// Describes how a single entity differs between two branches.
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
185pub struct EntityDiff {
186 /// The entity identifier.
187 pub entity_id: String,
188 /// The entity type represented by the diff.
189 pub entity_type: EntityType,
190 /// The high-level kind of change.
191 pub diff_kind: DiffKind,
192 /// The per-field changes discovered for the entity.
193 pub field_diffs: Vec<FieldDiff>,
194}
195
196/// Describes the coarse-grained kind of change for an entity.
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
198pub enum DiffKind {
199 /// The entity only exists in the second branch.
200 Added,
201 /// The entity only exists in the first branch.
202 Removed,
203 /// The entity exists in both branches with field changes.
204 Modified,
205 /// The entity exists in both branches without changes.
206 Unchanged,
207}
208
209/// Describes a change to a specific field within an entity.
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
211pub struct FieldDiff {
212 /// The changed field name.
213 pub field: String,
214 /// The value before the change.
215 pub before: Value,
216 /// The value after the change.
217 pub after: Value,
218}
219
220/// Aggregated counters for a diff run.
221#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
222pub struct DiffStats {
223 /// The number of added entities.
224 pub added: u32,
225 /// The number of removed entities.
226 pub removed: u32,
227 /// The number of modified entities.
228 pub modified: u32,
229 /// The number of unchanged entities.
230 pub unchanged: u32,
231 /// The total number of compared entities.
232 pub total_entities: u32,
233}
234
235/// Describes a field-level merge conflict.
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
237pub struct MergeConflict {
238 /// The entity identifier that conflicted.
239 pub entity_id: String,
240 /// The entity type that conflicted.
241 pub entity_type: EntityType,
242 /// The base branch value.
243 pub base_value: Value,
244 /// The local branch value.
245 pub ours_value: Value,
246 /// The incoming branch value.
247 pub theirs_value: Value,
248 /// The list of conflicting field names.
249 pub conflicting_fields: Vec<String>,
250}
251
252/// Summarizes the outcome of a merge operation.
253#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
254pub struct MergeResult {
255 /// The source branch identifier.
256 pub source_branch_id: Uuid,
257 /// The target branch identifier.
258 pub target_branch_id: Uuid,
259 /// The base branch identifier used for the merge.
260 pub base_branch_id: Uuid,
261 /// The number of applied entity changes.
262 pub applied: u32,
263 /// The number of skipped entity changes.
264 pub skipped: u32,
265 /// The conflicts discovered during the merge.
266 pub conflicts: Vec<MergeConflict>,
267 /// The merge duration in milliseconds.
268 pub duration_ms: u64,
269 /// Indicates whether the merge completed successfully.
270 pub success: bool,
271}
272
273/// Identifies the logical entity tables that branch diffs and merges operate on.
274#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
275pub enum EntityType {
276 /// A memory record entity.
277 MemoryRecord,
278 /// A session entity.
279 Session,
280 /// A tool output entity.
281 ToolOutput,
282}
283
284impl EntityType {
285 /// Returns the SQLite table name associated with the entity type.
286 pub fn table_name(&self) -> &'static str {
287 match self {
288 Self::MemoryRecord => "memory_records",
289 Self::Session => "sessions",
290 Self::ToolOutput => "tool_outputs",
291 }
292 }
293
294 /// Parses a string representation into an entity type.
295 pub fn parse(value: &str) -> Option<Self> {
296 match value {
297 "memory_record" | "memory_records" => Some(Self::MemoryRecord),
298 "session" | "sessions" => Some(Self::Session),
299 "tool_output" | "tool_outputs" => Some(Self::ToolOutput),
300 _ => None,
301 }
302 }
303
304 /// Returns the canonical storage string for the entity type.
305 pub fn as_str(&self) -> &'static str {
306 match self {
307 Self::MemoryRecord => "memory_record",
308 Self::Session => "session",
309 Self::ToolOutput => "tool_output",
310 }
311 }
312}
313
314/// Describes a persisted commit log entry for a branch.
315#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
316pub struct CommitLogEntry {
317 /// The unique commit log entry identifier.
318 pub id: Uuid,
319 /// The branch the entry belongs to.
320 pub branch_id: Uuid,
321 /// The logical entity type included in the commit.
322 pub entity_type: Option<EntityType>,
323 /// The affected entity identifiers.
324 pub entity_ids: Vec<String>,
325 /// The operation kind recorded in the log.
326 pub op_kind: String,
327 /// The commit timestamp.
328 pub committed_at: DateTime<Utc>,
329 /// An optional commit message.
330 pub message: Option<String>,
331}
332
333/// Summarizes the outcome of a selective commit operation.
334#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
335pub struct CommitResult {
336 /// The number of entity rows committed to the target.
337 pub committed_entity_count: u32,
338 /// The number of individual fields updated.
339 pub fields_updated: u32,
340 /// The commit duration in milliseconds.
341 pub duration_ms: u64,
342 /// The target branch the entities were committed into.
343 pub target_branch_id: Uuid,
344 /// The timestamp the commit was recorded.
345 pub committed_at: DateTime<Utc>,
346}
347
348/// Describes the observable outcome of a sandbox simulation run.
349#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
350pub struct SimulationOutcome {
351 /// The raw value returned by the agent function.
352 pub agent_output: Value,
353 /// The number of entity write operations recorded during the run.
354 pub ops_executed: u32,
355 /// The run duration in milliseconds.
356 pub duration_ms: u64,
357}
358
359/// Aggregated per-workspace metrics report.
360#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
361pub struct WorkspaceReport {
362 /// The total number of branches in the workspace.
363 pub branch_count: u32,
364 /// The combined on-disk size of all branch databases in bytes.
365 pub total_disk_bytes: u64,
366 /// The mean divergence score across all live branches.
367 pub avg_divergence_score: f64,
368 /// The branch with the highest op count, if any.
369 pub most_active_branch: Option<Uuid>,
370 /// Branch identifiers with no activity in the past seven days.
371 pub stale_branch_ids: Vec<Uuid>,
372 /// The timestamp this report was generated.
373 pub report_at: DateTime<Utc>,
374}