1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Error types for branch orchestration.
use std::path::PathBuf;
use thiserror::Error;
use uuid::Uuid;
/// The result type used across the branch engine.
pub type BranchResult<T> = Result<T, BranchError>;
/// Enumerates errors produced by branching, snapshotting, merging, and metrics code.
#[derive(Debug, Error)]
pub enum BranchError {
/// Indicates a runtime configuration is invalid.
#[error("invalid config: {0}")]
InvalidConfig(String),
/// Indicates permission checks denied the operation.
#[error("permission denied: {0}")]
PermissionDenied(String),
/// Indicates a branch name failed strict path-safe validation.
#[error("invalid branch name")]
InvalidBranchName,
/// Indicates the workspace has reached its configured branch limit.
#[error("branch limit exceeded")]
BranchLimitExceeded,
/// Wraps SQLx database failures.
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
/// Wraps SQLx migration failures.
#[error("migration error: {0}")]
Migration(#[from] sqlx::migrate::MigrateError),
/// Wraps filesystem errors.
#[error("io error: {0}")]
Io(#[from] std::io::Error),
/// Wraps JSON serialization errors.
#[error("serialization error: {0}")]
Serialization(#[from] serde_json::Error),
/// Indicates a branch lookup failed.
#[error("branch not found: {0}")]
BranchNotFound(Uuid),
/// Indicates a workspace already has a branch with the same logical name.
#[error("branch already exists: {0}")]
BranchAlreadyExists(String),
/// Indicates an operation requires an active branch.
#[error("branch {id} is not active: {status}")]
BranchNotActive {
/// The branch identifier that failed the active-state check.
id: Uuid,
/// The current lifecycle status string for the branch.
status: String,
},
/// Indicates snapshot creation or restore failed.
#[error("snapshot failed for branch {branch_id}: {reason}")]
SnapshotFailed {
/// The branch identifier associated with the snapshot failure.
branch_id: Uuid,
/// The human-readable failure reason.
reason: String,
},
/// Indicates a snapshot file failed integrity validation.
#[error("snapshot corrupt for branch {branch_id} at {path:?}")]
SnapshotCorrupt {
/// The branch identifier associated with the corrupt snapshot.
branch_id: Uuid,
/// The path to the corrupt snapshot file.
path: PathBuf,
},
/// Indicates a required snapshot hash sidecar file is missing.
#[error("snapshot hash missing for branch {branch_id} at {path:?}")]
SnapshotHashMissing {
/// The branch identifier associated with the missing hash sidecar.
branch_id: Uuid,
/// The path to the expected sidecar hash file.
path: PathBuf,
},
/// Indicates merge conflicts were not fully resolved.
#[error("merge conflicts unresolved for entities: {entity_ids:?}")]
MergeConflictUnresolved {
/// The entity identifiers that still have unresolved conflicts.
entity_ids: Vec<String>,
},
/// Indicates two branches cannot be merged with the selected base.
#[error("merge incompatible base for ours={ours} theirs={theirs}: {reason}")]
MergeIncompatibleBase {
/// The local branch identifier.
ours: Uuid,
/// The incoming branch identifier.
theirs: Uuid,
/// The reason the base is incompatible.
reason: String,
},
/// Indicates a lineage edge would create a DAG cycle.
#[error("dag cycle detected from {from} to {to}")]
DagCycle {
/// The proposed source branch id.
from: Uuid,
/// The proposed destination branch id.
to: Uuid,
},
/// Indicates a branch operation would introduce a lineage cycle.
#[error("cycle detected from {from} to {to}")]
CycleDetected {
/// The proposed source branch id.
from: Uuid,
/// The proposed destination branch id.
to: Uuid,
},
/// Indicates a DAG node lookup failed.
#[error("dag node not found: {0}")]
DagNodeNotFound(Uuid),
/// Indicates diff extraction failed.
#[error("diff failed between {branch_a} and {branch_b}: {reason}")]
DiffFailed {
/// The first branch identifier in the failed diff.
branch_a: Uuid,
/// The second branch identifier in the failed diff.
branch_b: Uuid,
/// The human-readable diff failure reason.
reason: String,
},
/// Indicates a selective commit failed validation.
#[error("commit validation failed for branch {branch_id}: {violations:?}")]
CommitValidationFailed {
/// The branch identifier whose commit was rejected.
branch_id: Uuid,
/// The list of validation violations.
violations: Vec<String>,
},
/// Indicates sandbox execution failed.
#[error("sandbox error: {0}")]
SandboxError(String),
/// Indicates metrics handling failed.
#[error("metrics error: {0}")]
MetricsError(String),
/// Indicates a branch name failed validation.
#[error("naming error: {0}")]
NamingError(String),
/// Indicates an orphaned snapshot path was encountered.
#[error("orphaned snapshot: {0:?}")]
OrphanedSnapshot(PathBuf),
}
impl BranchError {
/// Returns true if this error indicates a missing branch or DAG node.
pub fn is_not_found(&self) -> bool {
matches!(self, Self::BranchNotFound(_) | Self::DagNodeNotFound(_))
}
/// Returns true if this error represents an unresolved merge conflict.
pub fn is_merge_conflict(&self) -> bool {
matches!(
self,
Self::MergeConflictUnresolved { .. } | Self::MergeIncompatibleBase { .. }
)
}
/// Returns true if this error belongs to the snapshot subsystem.
pub fn is_snapshot_error(&self) -> bool {
matches!(
self,
Self::SnapshotFailed { .. }
| Self::SnapshotCorrupt { .. }
| Self::SnapshotHashMissing { .. }
| Self::OrphanedSnapshot(_)
)
}
/// Returns true if this error belongs to DAG management.
pub fn is_dag_error(&self) -> bool {
matches!(
self,
Self::DagCycle { .. } | Self::CycleDetected { .. } | Self::DagNodeNotFound(_)
)
}
}