Skip to main content

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}