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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
// === crates/m1nd-core/src/error.rs ===
use crate::types::{EdgeIdx, Generation, NodeId};
/// Central error type covering all failure modes from 05-HARDENING-SYNTHESIS.
/// Each variant references its FM-ID for traceability.
#[derive(Debug, thiserror::Error)]
pub enum M1ndError {
// --- Graph integrity ---
/// FM-ACT-011: Edge references a node index that does not exist.
#[error("dangling edge: edge {edge:?} references non-existent node {node:?}")]
DanglingEdge { edge: EdgeIdx, node: NodeId },
/// FM-PL-006: Graph structure changed since engine was initialised.
#[error("graph generation mismatch: expected {expected:?}, actual {actual:?}")]
GraphGenerationMismatch {
expected: Generation,
actual: Generation,
},
/// FM-ACT-016: Attempted to add a node whose interned ID already exists.
#[error("duplicate node: interned ID {0:?}")]
DuplicateNode(NodeId),
/// Graph not finalised — CSR not built yet.
#[error("graph not finalised: call Graph::finalize() before queries")]
GraphNotFinalized,
/// Graph is empty (zero nodes).
#[error("graph is empty")]
EmptyGraph,
// --- Numerical safety ---
/// FM-PL-001: Non-finite value detected at a NaN firewall boundary.
#[error("non-finite value at firewall: node={node:?}, value={value}")]
NonFiniteActivation { node: NodeId, value: f32 },
/// FM-ACT-012: A tuneable parameter is outside its valid range.
#[error("parameter out of range: {name} = {value} (expected {range})")]
ParameterOutOfRange {
name: &'static str,
value: f64,
range: &'static str,
},
/// FM-RES-001: Zero or negative wavelength/frequency supplied.
#[error("non-positive resonance parameter: {name} = {value}")]
NonPositiveResonanceParam { name: &'static str, value: f32 },
// --- Resource exhaustion ---
/// FM-RES-004: Pulse propagation exceeded budget.
#[error("pulse budget exhausted: {budget} pulses processed")]
PulseBudgetExhausted { budget: u64 },
/// FM-TMP-005: Causal chain DFS exceeded budget.
#[error("chain budget exhausted: {budget} chains generated")]
ChainBudgetExhausted { budget: u64 },
/// FM-TMP-001: Co-change sparse matrix exceeded entry budget.
#[error("matrix entry budget exhausted: {budget} entries")]
MatrixBudgetExhausted { budget: u64 },
/// FM-ING-002: Ingestion exceeded timeout.
#[error("ingestion timeout after {elapsed_s:.1}s")]
IngestionTimeout { elapsed_s: f64 },
/// A supervising runtime cooperatively cancelled ingestion. The error is
/// intentionally data-free so parallel workers always surface one stable,
/// matchable outcome rather than a race-dependent file or phase name.
#[error("ingestion cancelled")]
IngestionCancelled,
/// FM-ING-002: Ingestion exceeded node count budget.
#[error("ingestion node budget exhausted: {budget} nodes")]
IngestionNodeBudget { budget: u64 },
/// FM-TOP-014: Fingerprint pair budget exceeded.
#[error("fingerprint pair budget exhausted: {budget} pairs")]
FingerprintPairBudget { budget: u64 },
// --- Analysis quality ---
/// FM-XLR-010: XLR cancelled all signal — fallback to hot-only.
#[error("XLR over-cancellation: all signal cancelled")]
XlrOverCancellation,
/// FM-TOP-003: Louvain community detection did not converge.
#[error("Louvain non-convergence after {passes} passes")]
LouvainNonConvergence { passes: u32 },
/// FM-TOP-010: Power iteration may have diverged.
#[error("spectral analysis: power iteration divergence suspected")]
SpectralDivergence,
/// FM-RES-020: Division by zero in normalization (max_amp == 0).
#[error("resonance normalization: max amplitude is zero")]
ResonanceZeroAmplitude,
/// FM-ACT-019: Atomic CAS retry limit exceeded during concurrent weight update.
#[error("CAS retry limit ({limit}) exceeded at edge {edge:?}")]
CasRetryExhausted { edge: EdgeIdx, limit: u32 },
// --- Ingestion ---
/// FM-ING-003: File encoding could not be determined.
#[error("encoding detection failed for {path} (confidence={confidence:.2})")]
EncodingDetectionFailed { path: String, confidence: f32 },
/// FM-ING-004: Binary file detected and skipped.
#[error("binary file skipped: {path}")]
BinaryFileSkipped { path: String },
/// FM-ING-008: Label collision — multiple nodes share a label.
#[error("label collision: {label} maps to {count} nodes")]
LabelCollision { label: String, count: usize },
/// A source-scoped refresh cannot prove that its dependency/ownership
/// closure is complete. The caller must stage a governed full projection
/// rebuild instead of silently applying an incomplete incremental graph.
#[error("full reindex required: {reason}")]
FullReindexRequired { reason: String },
// --- Persistence ---
/// FM-PL-007: Corrupt state file on load.
#[error("corrupt persistence state: {reason}")]
CorruptState { reason: String },
/// FM-PL-009: Schema drift — edge identity mismatch on import.
#[error("schema drift on import: {reason}")]
SchemaDrift { reason: String },
/// OPTIONAL `embed` feature: failed to load or run the static embedding model.
#[error("embed error: {0}")]
EmbedError(String),
// --- Counterfactual ---
/// FM-CF-001: Seed node was in the removal set.
#[error("counterfactual seed overlap: seed {node:?} is in the removal set")]
CounterfactualSeedOverlap { node: NodeId },
// --- Perspective / Lock / Navigation (12-PERSPECTIVE-SYNTHESIS Theme 3) ---
/// Theme 3: Unknown tool name in dispatch.
#[error("unknown tool: {name}")]
UnknownTool { name: String },
/// Theme 3: Invalid parameters for a tool call.
#[error("invalid params for {tool}: {detail}")]
InvalidParams { tool: String, detail: String },
/// Theme 3: Perspective does not exist for agent.
#[error("perspective not found: {perspective_id} for agent {agent_id}")]
PerspectiveNotFound {
perspective_id: String,
agent_id: String,
},
/// Theme 3: Perspective route set is stale (generation mismatch).
#[error(
"perspective stale: {perspective_id} expected gen {expected_gen}, actual {actual_gen}"
)]
PerspectiveStale {
perspective_id: String,
expected_gen: u64,
actual_gen: u64,
},
/// Theme 3: Agent exceeded max perspective count.
#[error("perspective limit exceeded for agent {agent_id}: {current}/{limit}")]
PerspectiveLimitExceeded {
agent_id: String,
current: usize,
limit: usize,
},
/// Theme 3: Route set version mismatch (stale cached routes).
#[error("route set stale: version {route_set_version}, current {current_version}")]
RouteSetStale {
route_set_version: u64,
current_version: u64,
},
/// Theme 3: Route not found in perspective.
#[error("route not found: {route_id} in perspective {perspective_id}")]
RouteNotFound {
route_id: String,
perspective_id: String,
},
/// Theme 3: Cannot navigate back — already at root.
#[error("navigation at root: perspective {perspective_id}")]
NavigationAtRoot { perspective_id: String },
/// Theme 3: Branch depth limit exceeded.
#[error("branch depth exceeded in {perspective_id}: depth {depth}/{limit}")]
BranchDepthExceeded {
perspective_id: String,
depth: usize,
limit: usize,
},
/// Theme 3: Lock not found.
#[error("lock not found: {lock_id}")]
LockNotFound { lock_id: String },
/// Theme 3: Lock ownership violation.
#[error("lock ownership violation: {lock_id} owned by {owner}, called by {caller}")]
LockOwnership {
lock_id: String,
owner: String,
caller: String,
},
/// Theme 3: Lock scope too large (BFS budget exceeded).
#[error("lock scope too large: {node_count} nodes exceeds cap of {cap}")]
LockScopeTooLarge { node_count: usize, cap: usize },
/// Theme 3: Agent exceeded max lock count.
#[error("lock limit exceeded for agent {agent_id}: {current}/{limit}")]
LockLimitExceeded {
agent_id: String,
current: usize,
limit: usize,
},
/// Theme 3: Watcher strategy not supported (e.g. Periodic in V1).
#[error("watch strategy not supported: {strategy}")]
WatchStrategyNotSupported { strategy: String },
/// Theme 3: Affinity computation exceeded time budget.
#[error("affinity timeout: {elapsed_ms:.1}ms exceeded budget of {budget_ms:.1}ms")]
AffinityTimeout { elapsed_ms: f64, budget_ms: f64 },
// --- Antibody ---
/// FM-AB-001: Antibody pattern specificity below minimum threshold.
#[error("pattern too broad: specificity {specificity:.2} below minimum {minimum:.2}")]
PatternTooBroad { specificity: f32, minimum: f32 },
/// Antibody not found by ID.
#[error("antibody not found: {id}")]
AntibodyNotFound { id: String },
/// Antibody storage limit exceeded.
#[error("antibody limit exceeded: {current}/{limit}")]
AntibodyLimitExceeded { current: usize, limit: usize },
// --- Epidemic ---
/// Epidemic burnout: too many nodes infected too fast.
#[error("epidemic burnout: {infected_pct:.1}% infected in {iteration} iterations")]
EpidemicBurnout { infected_pct: f32, iteration: u32 },
/// No valid infected nodes provided for epidemic simulation.
#[error("no valid infected nodes")]
NoValidInfectedNodes,
// --- Flow ---
/// No entry points found for flow simulation.
#[error("no entry points found for flow simulation")]
NoEntryPoints,
// --- Layers ---
/// Layer level not found in detection result.
#[error("layer not found: level {level}")]
LayerNotFound { level: u8 },
// --- Ingestion (runtime) ---
/// Tree-sitter or extractor runtime error.
#[error("ingest error: {0}")]
IngestError(String),
// --- I/O ---
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("serialization error: {0}")]
Serde(#[from] serde_json::Error),
#[error("persistence failed: {0}")]
PersistenceFailed(String),
}
/// Convenience alias used throughout the crate.
pub type M1ndResult<T> = Result<T, M1ndError>;