1use std::fmt;
2
3use engram_consolidate::ConsolidateError;
4use engram_hnsw::HnswError;
5use engram_llm_client::ApiError;
6use engram_storage::StorageError;
7
8#[derive(Debug)]
9pub enum CoreError {
10 ConfigNotFound,
11 ConfigParseError(String),
12 InvalidProvider(String),
13 IndexCorrupted(String),
14 RebuildFailed(String),
15 SocketError(String),
16 DispatchError(String),
17 ConfigReadOnly,
18 ExportFailed(String),
19 ImportVersionMismatch(u64),
20 ImportFailed(String),
21 InitFailed(String),
22 Storage(StorageError),
23 Hnsw(HnswError),
24 Api(ApiError),
25 TrainerFailed(String),
26 TrainerTimeout,
27 TrainerMalformedOutput(String),
28 ProjectDirNotFound,
29 LegacyDatabaseDetected {
30 legacy_path: String,
31 project_path: String,
32 },
33 MigrationSourceNotFound,
34 MigrationFailed(String),
35 EmbeddingModelMismatch {
36 stored: String,
37 configured: String,
38 },
39 IndexHashCollision {
40 hash: u64,
41 existing_id: String,
42 conflicting_id: String,
43 },
44 ConfigValidation(String),
45 Consolidation(ConsolidateError),
46}
47
48impl fmt::Display for CoreError {
49 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::ConfigNotFound => {
52 write!(formatter, "[6001] config not found")
53 }
54 Self::ConfigParseError(message) => {
55 write!(formatter, "[6002] config parse error: {message}")
56 }
57 Self::InvalidProvider(message) => {
58 write!(formatter, "[6003] invalid provider: {message}")
59 }
60 Self::IndexCorrupted(message) => {
61 write!(formatter, "[6004] index corrupted: {message}")
62 }
63 Self::RebuildFailed(message) => {
64 write!(formatter, "[6005] rebuild failed: {message}")
65 }
66 Self::SocketError(message) => {
67 write!(formatter, "[6006] socket error: {message}")
68 }
69 Self::DispatchError(message) => {
70 write!(formatter, "[6007] dispatch error: {message}")
71 }
72 Self::ConfigReadOnly => {
73 write!(formatter, "[6008] config is read-only")
74 }
75 Self::ExportFailed(message) => {
76 write!(formatter, "[6009] export failed: {message}")
77 }
78 Self::ImportVersionMismatch(version) => {
79 write!(
80 formatter,
81 "[6010] import version mismatch: expected 1, got {version}"
82 )
83 }
84 Self::ImportFailed(message) => {
85 write!(formatter, "[6011] import failed: {message}")
86 }
87 Self::InitFailed(message) => {
88 write!(formatter, "[6012] init failed: {message}")
89 }
90 Self::TrainerFailed(message) => {
91 write!(formatter, "[6013] trainer failed: {message}")
92 }
93 Self::TrainerTimeout => {
94 write!(formatter, "[6014] trainer timeout")
95 }
96 Self::TrainerMalformedOutput(message) => {
97 write!(formatter, "[6015] trainer malformed output: {message}")
98 }
99 Self::ProjectDirNotFound => {
100 write!(
101 formatter,
102 "[6016] project directory not found: no .engram/ in cwd or ancestors (run 'engram init')"
103 )
104 }
105 Self::LegacyDatabaseDetected {
106 legacy_path,
107 project_path,
108 } => {
109 write!(
110 formatter,
111 "[6017] legacy global database detected at {legacy_path}. Run `engram migrate` to import into project {project_path}, or `engram init` to start fresh."
112 )
113 }
114 Self::MigrationSourceNotFound => {
115 write!(
116 formatter,
117 "[6018] migration source not found: no legacy database at ~/.engram/engram.db (nothing to migrate)"
118 )
119 }
120 Self::MigrationFailed(message) => {
121 write!(formatter, "[6019] migration failed: {message}")
122 }
123 Self::EmbeddingModelMismatch { stored, configured } => {
124 write!(
125 formatter,
126 "[6020] embedding model mismatch: database was last embedded with `{stored}`, but config specifies `{configured}`. Run `engram reembed` to re-compute embeddings, then restart the daemon."
127 )
128 }
129 Self::IndexHashCollision {
130 hash,
131 existing_id,
132 conflicting_id,
133 } => {
134 write!(
135 formatter,
136 "[6021] index hash collision: hash {hash:#x} already mapped to '{existing_id}', refusing '{conflicting_id}'"
137 )
138 }
139 Self::ConfigValidation(message) => {
140 write!(formatter, "[6022] invalid configuration: {message}")
141 }
142 Self::Storage(error) => error.fmt(formatter),
143 Self::Hnsw(error) => error.fmt(formatter),
144 Self::Api(error) => error.fmt(formatter),
145 Self::Consolidation(error) => error.fmt(formatter),
146 }
147 }
148}
149
150impl std::error::Error for CoreError {
151 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
152 match self {
153 Self::Storage(error) => Some(error),
154 Self::Hnsw(error) => Some(error),
155 Self::Api(error) => Some(error),
156 Self::Consolidation(error) => Some(error),
157 _ => None,
158 }
159 }
160}
161
162impl From<StorageError> for CoreError {
163 fn from(error: StorageError) -> Self {
164 Self::Storage(error)
165 }
166}
167
168impl From<HnswError> for CoreError {
169 fn from(error: HnswError) -> Self {
170 Self::Hnsw(error)
171 }
172}
173
174impl From<ApiError> for CoreError {
175 fn from(error: ApiError) -> Self {
176 Self::Api(error)
177 }
178}
179
180impl From<ConsolidateError> for CoreError {
181 fn from(error: ConsolidateError) -> Self {
182 Self::Consolidation(error)
183 }
184}