1use std::fmt;
4
5use thiserror::Error;
6
7pub type RuntimeResult<T> = Result<T, RuntimeError>;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct MissingPackDependency {
13 pub from: String,
14 pub requires: String,
15}
16
17impl fmt::Display for MissingPackDependency {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 write!(
20 f,
21 "pack '{}' requires '{}', but '{}' is not in the loaded pack set",
22 self.from, self.requires, self.requires
23 )
24 }
25}
26
27impl std::error::Error for MissingPackDependency {}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct MissingPackDependencies {
32 pub missing: Vec<MissingPackDependency>,
33}
34
35impl fmt::Display for MissingPackDependencies {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 let parts: Vec<String> = self.missing.iter().map(ToString::to_string).collect();
38 write!(f, "{}", parts.join("; "))
39 }
40}
41
42impl std::error::Error for MissingPackDependencies {}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct CircularPackDependency {
47 pub cycle: Vec<String>,
48}
49
50impl fmt::Display for CircularPackDependency {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(
53 f,
54 "circular dependency detected among packs: {}",
55 self.cycle.join(" -> ")
56 )
57 }
58}
59
60impl std::error::Error for CircularPackDependency {}
61
62#[derive(Debug, Error)]
68pub enum RuntimeError {
69 #[error("storage: {0}")]
70 Storage(#[from] khive_storage::StorageError),
71
72 #[error("sqlite: {0}")]
73 Sqlite(#[from] khive_db::SqliteError),
74
75 #[error("query: {0}")]
76 Query(#[from] khive_query::QueryError),
77
78 #[error("not found: {0}")]
79 NotFound(String),
80
81 #[error("invalid input: {0}")]
82 InvalidInput(String),
83
84 #[error("unconfigured: {0} is not set")]
85 Unconfigured(String),
86
87 #[error("unknown embedding model: {0}")]
88 UnknownModel(String),
89
90 #[error("embedding: {0}")]
91 Embedding(#[from] lattice_embed::EmbedError),
92
93 #[error("ambiguous: {0}")]
94 Ambiguous(String),
95
96 #[error("fusion: {0}")]
97 Fusion(#[from] khive_fusion::FuseError),
98
99 #[error("internal: {0}")]
100 Internal(String),
101
102 #[error("missing pack dependency: {0}")]
103 MissingPackDependency(MissingPackDependency),
104
105 #[error("missing pack dependencies: {0}")]
106 MissingPackDependencies(MissingPackDependencies),
107
108 #[error("{0}")]
109 CircularPackDependency(CircularPackDependency),
110
111 #[error("pack '{name}' registered twice (indices {first_idx} and {second_idx})")]
112 PackRedeclared {
113 name: String,
114 first_idx: usize,
115 second_idx: usize,
116 },
117
118 #[error(
122 "verb collision: verb {verb:?} declared by both pack {first_pack:?} and pack \
123 {second_pack:?}; rename one handler or use Visibility::Subhandler for internal verbs"
124 )]
125 VerbCollision {
126 verb: String,
127 first_pack: String,
128 second_pack: String,
129 },
130
131 #[error("permission denied for verb {verb:?}: {reason}")]
137 PermissionDenied { verb: String, reason: String },
138
139 #[error("{0}")]
143 Khive(khive_types::KhiveError),
144
145 #[error("not found in this namespace")]
150 NamespaceMismatch { id: uuid::Uuid },
151
152 #[error("ambiguous prefix {prefix:?}: matches {}", format_uuid_list(matches))]
158 AmbiguousPrefix {
159 prefix: String,
160 matches: Vec<uuid::Uuid>,
161 },
162
163 #[error(
168 "cross-backend merge is not supported: \
169 into_id {into_id} is on backend '{into_backend}', \
170 from_id {from_id} is on backend '{from_backend}'. \
171 Both entities must be on the same backend to merge."
172 )]
173 CrossBackendMergeUnsupported {
174 into_id: uuid::Uuid,
175 from_id: uuid::Uuid,
176 into_backend: String,
177 from_backend: String,
178 },
179
180 #[error("unknown remote: {name:?}")]
183 UnknownRemote { name: String },
184
185 #[error("remote cache missing for remote={remote:?} namespace={namespace:?}")]
187 RemoteCacheMissing { remote: String, namespace: String },
188
189 #[error("ambiguous id {id:?}: matched {count} records")]
191 AmbiguousId { id: String, count: usize },
192
193 #[error("cross-namespace write denied: cannot write to remote namespace {namespace:?}")]
195 CrossNamespaceWrite { namespace: String },
196
197 #[error("remote fetch error for remote={remote:?}: {message}")]
199 RemoteFetchError { remote: String, message: String },
200
201 #[error(
207 "write budget exceeded: max_new_entries={max_new_entries}, \
208 attempted_new_entries={attempted_new_entries}"
209 )]
210 WriteBudgetExceeded {
211 max_new_entries: u64,
212 attempted_new_entries: u64,
213 },
214
215 #[error("write blocked: {0}")]
221 SecretDetected(crate::secret_gate::SecretMatch),
222}
223
224fn format_uuid_list(uuids: &[uuid::Uuid]) -> String {
225 let shorts: Vec<String> = uuids
226 .iter()
227 .map(|u| u.to_string()[..8].to_string())
228 .collect();
229 shorts.join(", ")
230}
231
232impl From<khive_types::KhiveError> for RuntimeError {
233 fn from(e: khive_types::KhiveError) -> Self {
234 Self::Khive(e)
235 }
236}