khive_vcs/error.rs
1// Copyright 2026 Haiyang Li. Licensed under Apache-2.0.
2//
3//! Error types for the VCS layer.
4//!
5//! Remote-server and custom-push/pull error variants (`RemoteUnreachable`,
6//! `AuthFailed`, `NonFastForward`, `MergeRequired`) were removed: git is the
7//! remote protocol; there is no custom `khive-sync` server.
8//! `MergeNotImplemented` was removed because the custom merge engine is
9//! superseded for v1.
10
11use thiserror::Error;
12
13use crate::types::SnapshotId;
14
15/// Errors that can occur in the khive VCS layer (snapshot hashing, NDJSON sync, remote fetch).
16#[derive(Debug, Error)]
17pub enum VcsError {
18 /// The archive stored at the remote has a different hash than expected.
19 /// Indicates corruption or tampering.
20 #[error("hash mismatch: expected {expected}, actual {actual}")]
21 HashMismatch {
22 expected: SnapshotId,
23 actual: SnapshotId,
24 },
25
26 /// `checkout` was blocked because there are uncommitted changes.
27 /// Pass `force: true` to discard them.
28 #[error("uncommitted changes: {count} entities/edges modified since last commit")]
29 UncommittedChanges { count: usize },
30
31 /// A `SnapshotId` string failed validation.
32 #[error("invalid snapshot id: {0}")]
33 InvalidSnapshotId(String),
34
35 /// A branch name failed validation (must match `^[a-zA-Z0-9_-]{1,64}$`).
36 #[error("invalid branch name: {0:?}")]
37 InvalidBranchName(String),
38
39 /// A remote name failed validation (must be a single path segment
40 /// matching `[A-Za-z0-9._-]+`, and not `.` or `..`). Rejected before any
41 /// filesystem path is built from it, to prevent path traversal into
42 /// `.khive/kg/remotes/`.
43 #[error("invalid remote name {0:?}: must be one path segment [A-Za-z0-9._-]+ and not . or ..")]
44 InvalidRemoteName(String),
45
46 /// An underlying storage operation failed.
47 #[error("storage: {0}")]
48 Storage(String),
49
50 /// JSON serialization or deserialization failed.
51 #[error("json: {0}")]
52 Json(#[from] serde_json::Error),
53
54 /// An I/O operation failed (file system).
55 #[error("io: {0}")]
56 Io(#[from] std::io::Error),
57
58 /// An unexpected internal error.
59 #[error("internal: {0}")]
60 Internal(String),
61}
62
63impl From<khive_runtime::error::RuntimeError> for VcsError {
64 fn from(e: khive_runtime::error::RuntimeError) -> Self {
65 VcsError::Storage(e.to_string())
66 }
67}