Skip to main content

icydb_core/error/
mod.rs

1//! Module: error
2//!
3//! Defines the canonical runtime error taxonomy for `icydb-core`.
4//! This module owns the shared error classes, origins, details, and
5//! constructor entry points used across storage, planning, execution, and
6//! serialization boundaries.
7
8#[cfg(test)]
9mod tests;
10
11use crate::serialize::{SerializeError, SerializeErrorKind};
12use std::fmt;
13use thiserror::Error as ThisError;
14
15// ============================================================================
16// INTERNAL ERROR TAXONOMY — ARCHITECTURAL CONTRACT
17// ============================================================================
18//
19// This file defines the canonical runtime error classification system for
20// icydb-core. It is the single source of truth for:
21//
22//   • ErrorClass   (semantic domain)
23//   • ErrorOrigin  (subsystem boundary)
24//   • Structured detail payloads
25//   • Canonical constructor entry points
26//
27// -----------------------------------------------------------------------------
28// DESIGN INTENT
29// -----------------------------------------------------------------------------
30//
31// 1. InternalError is a *taxonomy carrier*, not a formatting utility.
32//
33//    - ErrorClass represents semantic meaning (corruption, invariant_violation,
34//      unsupported, etc).
35//    - ErrorOrigin represents the subsystem boundary (store, index, query,
36//      executor, serialize, interface, etc).
37//    - The (class, origin) pair must remain stable and intentional.
38//
39// 2. Call sites MUST prefer canonical constructors.
40//
41//    Do NOT construct errors manually via:
42//        InternalError::new(class, origin, ...)
43//    unless you are defining a new canonical helper here.
44//
45//    If a pattern appears more than once, centralize it here.
46//
47// 3. Constructors in this file must represent real architectural boundaries.
48//
49//    Add a new helper ONLY if it:
50//
51//      • Encodes a cross-cutting invariant,
52//      • Represents a subsystem boundary,
53//      • Or prevents taxonomy drift across call sites.
54//
55//    Do NOT add feature-specific helpers.
56//    Do NOT add one-off formatting helpers.
57//    Do NOT turn this file into a generic message factory.
58//
59// 4. ErrorDetail must align with ErrorOrigin.
60//
61//    If detail is present, it MUST correspond to the origin.
62//    Do not attach mismatched detail variants.
63//
64// 5. Plan-layer errors are NOT runtime failures.
65//
66//    PlanError and CursorPlanError must be translated into
67//    executor/query invariants via the canonical mapping functions.
68//    Do not leak plan-layer error types across execution boundaries.
69//
70// 6. Preserve taxonomy stability.
71//
72//    Do NOT:
73//      • Merge error classes.
74//      • Reclassify corruption as internal.
75//      • Downgrade invariant violations.
76//      • Introduce ambiguous class/origin combinations.
77//
78//    Any change to ErrorClass or ErrorOrigin is an architectural change
79//    and must be reviewed accordingly.
80//
81// -----------------------------------------------------------------------------
82// NON-GOALS
83// -----------------------------------------------------------------------------
84//
85// This is NOT:
86//
87//   • A public API contract.
88//   • A generic error abstraction layer.
89//   • A feature-specific message builder.
90//   • A dumping ground for temporary error conversions.
91//
92// -----------------------------------------------------------------------------
93// MAINTENANCE GUIDELINES
94// -----------------------------------------------------------------------------
95//
96// When modifying this file:
97//
98//   1. Ensure classification semantics remain consistent.
99//   2. Avoid constructor proliferation.
100//   3. Prefer narrow, origin-specific helpers over ad-hoc new(...).
101//   4. Keep formatting minimal and standardized.
102//   5. Keep this file boring and stable.
103//
104// If this file grows rapidly, something is wrong at the call sites.
105//
106// ============================================================================
107
108///
109/// InternalError
110///
111/// Structured runtime error with a stable internal classification.
112/// Not a stable API; intended for internal use and may change without notice.
113///
114
115#[derive(Debug, ThisError)]
116#[error("{message}")]
117pub struct InternalError {
118    pub(crate) class: ErrorClass,
119    pub(crate) origin: ErrorOrigin,
120    pub(crate) message: String,
121
122    /// Optional structured error detail.
123    /// The variant (if present) must correspond to `origin`.
124    pub(crate) detail: Option<ErrorDetail>,
125}
126
127impl InternalError {
128    /// Construct an InternalError with optional origin-specific detail.
129    /// This constructor provides default StoreError details for certain
130    /// (class, origin) combinations but does not guarantee a detail payload.
131    #[cold]
132    #[inline(never)]
133    pub fn new(class: ErrorClass, origin: ErrorOrigin, message: impl Into<String>) -> Self {
134        let message = message.into();
135
136        let detail = match (class, origin) {
137            (ErrorClass::Corruption, ErrorOrigin::Store) => {
138                Some(ErrorDetail::Store(StoreError::Corrupt {
139                    message: message.clone(),
140                }))
141            }
142            (ErrorClass::InvariantViolation, ErrorOrigin::Store) => {
143                Some(ErrorDetail::Store(StoreError::InvariantViolation {
144                    message: message.clone(),
145                }))
146            }
147            _ => None,
148        };
149
150        Self {
151            class,
152            origin,
153            message,
154            detail,
155        }
156    }
157
158    /// Return the internal error class taxonomy.
159    #[must_use]
160    pub const fn class(&self) -> ErrorClass {
161        self.class
162    }
163
164    /// Return the internal error origin taxonomy.
165    #[must_use]
166    pub const fn origin(&self) -> ErrorOrigin {
167        self.origin
168    }
169
170    /// Return the rendered internal error message.
171    #[must_use]
172    pub fn message(&self) -> &str {
173        &self.message
174    }
175
176    /// Return the optional structured detail payload.
177    #[must_use]
178    pub const fn detail(&self) -> Option<&ErrorDetail> {
179        self.detail.as_ref()
180    }
181
182    /// Consume and return the rendered internal error message.
183    #[must_use]
184    pub fn into_message(self) -> String {
185        self.message
186    }
187
188    /// Construct an error while preserving an explicit class/origin taxonomy pair.
189    #[cold]
190    #[inline(never)]
191    pub(crate) fn classified(
192        class: ErrorClass,
193        origin: ErrorOrigin,
194        message: impl Into<String>,
195    ) -> Self {
196        Self::new(class, origin, message)
197    }
198
199    /// Rebuild this error with a new message while preserving class/origin taxonomy.
200    #[cold]
201    #[inline(never)]
202    pub(crate) fn with_message(self, message: impl Into<String>) -> Self {
203        Self::classified(self.class, self.origin, message)
204    }
205
206    /// Rebuild this error with a new origin while preserving class/message.
207    ///
208    /// Origin-scoped detail payloads are intentionally dropped when re-origining.
209    #[cold]
210    #[inline(never)]
211    pub(crate) fn with_origin(self, origin: ErrorOrigin) -> Self {
212        Self::classified(self.class, origin, self.message)
213    }
214
215    /// Construct an index-origin invariant violation.
216    #[cold]
217    #[inline(never)]
218    pub(crate) fn index_invariant(message: impl Into<String>) -> Self {
219        Self::new(
220            ErrorClass::InvariantViolation,
221            ErrorOrigin::Index,
222            message.into(),
223        )
224    }
225
226    /// Construct the canonical index field-count invariant for key building.
227    pub(crate) fn index_key_field_count_exceeds_max(
228        index_name: &str,
229        field_count: usize,
230        max_fields: usize,
231    ) -> Self {
232        Self::index_invariant(format!(
233            "index '{index_name}' has {field_count} fields (max {max_fields})",
234        ))
235    }
236
237    /// Construct the canonical index-key source-field-missing-on-model invariant.
238    pub(crate) fn index_key_item_field_missing_on_entity_model(field: &str) -> Self {
239        Self::index_invariant(format!(
240            "index key item field missing on entity model: {field}",
241        ))
242    }
243
244    /// Construct the canonical index-key source-field-missing-on-row invariant.
245    pub(crate) fn index_key_item_field_missing_on_lookup_row(field: &str) -> Self {
246        Self::index_invariant(format!(
247            "index key item field missing on lookup row: {field}",
248        ))
249    }
250
251    /// Construct the canonical index-expression source-type mismatch invariant.
252    pub(crate) fn index_expression_source_type_mismatch(
253        index_name: &str,
254        expression: impl fmt::Display,
255        expected: &str,
256        source_label: &str,
257    ) -> Self {
258        Self::index_invariant(format!(
259            "index '{index_name}' expression '{expression}' expected {expected} source value, got {source_label}",
260        ))
261    }
262
263    /// Construct a planner-origin invariant violation with the canonical
264    /// executor-boundary invariant prefix preserved in the message payload.
265    #[cold]
266    #[inline(never)]
267    pub(crate) fn planner_executor_invariant(reason: impl Into<String>) -> Self {
268        Self::new(
269            ErrorClass::InvariantViolation,
270            ErrorOrigin::Planner,
271            Self::executor_invariant_message(reason),
272        )
273    }
274
275    /// Construct a query-origin invariant violation with the canonical
276    /// executor-boundary invariant prefix preserved in the message payload.
277    #[cold]
278    #[inline(never)]
279    pub(crate) fn query_executor_invariant(reason: impl Into<String>) -> Self {
280        Self::new(
281            ErrorClass::InvariantViolation,
282            ErrorOrigin::Query,
283            Self::executor_invariant_message(reason),
284        )
285    }
286
287    /// Construct a cursor-origin invariant violation with the canonical
288    /// executor-boundary invariant prefix preserved in the message payload.
289    #[cold]
290    #[inline(never)]
291    pub(crate) fn cursor_executor_invariant(reason: impl Into<String>) -> Self {
292        Self::new(
293            ErrorClass::InvariantViolation,
294            ErrorOrigin::Cursor,
295            Self::executor_invariant_message(reason),
296        )
297    }
298
299    /// Construct an executor-origin invariant violation.
300    #[cold]
301    #[inline(never)]
302    pub(crate) fn executor_invariant(message: impl Into<String>) -> Self {
303        Self::new(
304            ErrorClass::InvariantViolation,
305            ErrorOrigin::Executor,
306            message.into(),
307        )
308    }
309
310    /// Construct an executor-origin internal error.
311    #[cold]
312    #[inline(never)]
313    pub(crate) fn executor_internal(message: impl Into<String>) -> Self {
314        Self::new(ErrorClass::Internal, ErrorOrigin::Executor, message.into())
315    }
316
317    /// Construct an executor-origin unsupported error.
318    #[cold]
319    #[inline(never)]
320    pub(crate) fn executor_unsupported(message: impl Into<String>) -> Self {
321        Self::new(
322            ErrorClass::Unsupported,
323            ErrorOrigin::Executor,
324            message.into(),
325        )
326    }
327
328    /// Construct an executor-origin save-preflight primary-key missing invariant.
329    pub(crate) fn mutation_entity_primary_key_missing(entity_path: &str, field_name: &str) -> Self {
330        Self::executor_invariant(format!(
331            "entity primary key field missing: {entity_path} field={field_name}",
332        ))
333    }
334
335    /// Construct an executor-origin save-preflight primary-key invalid-value invariant.
336    pub(crate) fn mutation_entity_primary_key_invalid_value(
337        entity_path: &str,
338        field_name: &str,
339        value: &crate::value::Value,
340    ) -> Self {
341        Self::executor_invariant(format!(
342            "entity primary key field has invalid value: {entity_path} field={field_name} value={value:?}",
343        ))
344    }
345
346    /// Construct an executor-origin save-preflight primary-key type mismatch invariant.
347    pub(crate) fn mutation_entity_primary_key_type_mismatch(
348        entity_path: &str,
349        field_name: &str,
350        value: &crate::value::Value,
351    ) -> Self {
352        Self::executor_invariant(format!(
353            "entity primary key field type mismatch: {entity_path} field={field_name} value={value:?}",
354        ))
355    }
356
357    /// Construct an executor-origin save-preflight primary-key identity mismatch invariant.
358    pub(crate) fn mutation_entity_primary_key_mismatch(
359        entity_path: &str,
360        field_name: &str,
361        field_value: &crate::value::Value,
362        identity_key: &crate::value::Value,
363    ) -> Self {
364        Self::executor_invariant(format!(
365            "entity primary key mismatch: {entity_path} field={field_name} field_value={field_value:?} id_key={identity_key:?}",
366        ))
367    }
368
369    /// Construct an executor-origin save-preflight field-missing invariant.
370    pub(crate) fn mutation_entity_field_missing(
371        entity_path: &str,
372        field_name: &str,
373        indexed: bool,
374    ) -> Self {
375        let indexed_note = if indexed { " (indexed)" } else { "" };
376
377        Self::executor_invariant(format!(
378            "entity field missing: {entity_path} field={field_name}{indexed_note}",
379        ))
380    }
381
382    /// Construct an executor-origin save-preflight field-type mismatch invariant.
383    pub(crate) fn mutation_entity_field_type_mismatch(
384        entity_path: &str,
385        field_name: &str,
386        value: &crate::value::Value,
387    ) -> Self {
388        Self::executor_invariant(format!(
389            "entity field type mismatch: {entity_path} field={field_name} value={value:?}",
390        ))
391    }
392
393    /// Construct an executor-origin generated-field authored-write rejection.
394    pub(crate) fn mutation_generated_field_explicit(entity_path: &str, field_name: &str) -> Self {
395        Self::executor_unsupported(format!(
396            "generated field may not be explicitly written: {entity_path} field={field_name}",
397        ))
398    }
399
400    /// Construct an executor-origin mutation result invariant.
401    ///
402    /// This constructor lands ahead of the public structural mutation surface,
403    /// so the library target may not route through it until that caller exists.
404    pub(crate) fn mutation_structural_after_image_invalid(
405        entity_path: &str,
406        data_key: impl fmt::Display,
407        detail: impl AsRef<str>,
408    ) -> Self {
409        Self::executor_invariant(format!(
410            "mutation result is invalid: {entity_path} key={data_key} ({})",
411            detail.as_ref(),
412        ))
413    }
414
415    /// Construct an executor-origin mutation unknown-field invariant.
416    pub(crate) fn mutation_structural_field_unknown(entity_path: &str, field_name: &str) -> Self {
417        Self::executor_invariant(format!(
418            "mutation field not found: {entity_path} field={field_name}",
419        ))
420    }
421
422    /// Construct an executor-origin save-preflight decimal-scale unsupported error.
423    pub(crate) fn mutation_decimal_scale_mismatch(
424        entity_path: &str,
425        field_name: &str,
426        expected_scale: impl fmt::Display,
427        actual_scale: impl fmt::Display,
428    ) -> Self {
429        Self::executor_unsupported(format!(
430            "decimal field scale mismatch: {entity_path} field={field_name} expected_scale={expected_scale} actual_scale={actual_scale}",
431        ))
432    }
433
434    /// Construct an executor-origin save-preflight set-encoding invariant.
435    pub(crate) fn mutation_set_field_list_required(entity_path: &str, field_name: &str) -> Self {
436        Self::executor_invariant(format!(
437            "set field must encode as Value::List: {entity_path} field={field_name}",
438        ))
439    }
440
441    /// Construct an executor-origin save-preflight set-canonicality invariant.
442    pub(crate) fn mutation_set_field_not_canonical(entity_path: &str, field_name: &str) -> Self {
443        Self::executor_invariant(format!(
444            "set field must be strictly ordered and deduplicated: {entity_path} field={field_name}",
445        ))
446    }
447
448    /// Construct an executor-origin save-preflight map-encoding invariant.
449    pub(crate) fn mutation_map_field_map_required(entity_path: &str, field_name: &str) -> Self {
450        Self::executor_invariant(format!(
451            "map field must encode as Value::Map: {entity_path} field={field_name}",
452        ))
453    }
454
455    /// Construct an executor-origin save-preflight map-entry invariant.
456    pub(crate) fn mutation_map_field_entries_invalid(
457        entity_path: &str,
458        field_name: &str,
459        detail: impl fmt::Display,
460    ) -> Self {
461        Self::executor_invariant(format!(
462            "map field entries violate map invariants: {entity_path} field={field_name} ({detail})",
463        ))
464    }
465
466    /// Construct an executor-origin save-preflight map-canonicality invariant.
467    pub(crate) fn mutation_map_field_entries_not_canonical(
468        entity_path: &str,
469        field_name: &str,
470    ) -> Self {
471        Self::executor_invariant(format!(
472            "map field entries are not in canonical deterministic order: {entity_path} field={field_name}",
473        ))
474    }
475
476    /// Construct a query-origin scalar page invariant for missing predicate slots.
477    pub(crate) fn scalar_page_predicate_slots_required() -> Self {
478        Self::query_executor_invariant("post-access filtering requires precompiled predicate slots")
479    }
480
481    /// Construct a query-origin scalar page invariant for ordering before filtering.
482    pub(crate) fn scalar_page_ordering_after_filtering_required() -> Self {
483        Self::query_executor_invariant("ordering must run after filtering")
484    }
485
486    /// Construct a query-origin scalar page invariant for missing order at the cursor boundary.
487    pub(crate) fn scalar_page_cursor_boundary_order_required() -> Self {
488        Self::query_executor_invariant("cursor boundary requires ordering")
489    }
490
491    /// Construct a query-origin scalar page invariant for cursor-before-ordering drift.
492    pub(crate) fn scalar_page_cursor_boundary_after_ordering_required() -> Self {
493        Self::query_executor_invariant("cursor boundary must run after ordering")
494    }
495
496    /// Construct a query-origin scalar page invariant for pagination-before-ordering drift.
497    pub(crate) fn scalar_page_pagination_after_ordering_required() -> Self {
498        Self::query_executor_invariant("pagination must run after ordering")
499    }
500
501    /// Construct a query-origin scalar page invariant for delete-limit-before-ordering drift.
502    pub(crate) fn scalar_page_delete_limit_after_ordering_required() -> Self {
503        Self::query_executor_invariant("delete limit must run after ordering")
504    }
505
506    /// Construct a query-origin load-runtime invariant for scalar-mode payload mismatch.
507    pub(crate) fn load_runtime_scalar_payload_required() -> Self {
508        Self::query_executor_invariant("scalar load mode must carry scalar runtime payload")
509    }
510
511    /// Construct a query-origin load-runtime invariant for grouped-mode payload mismatch.
512    pub(crate) fn load_runtime_grouped_payload_required() -> Self {
513        Self::query_executor_invariant("grouped load mode must carry grouped runtime payload")
514    }
515
516    /// Construct a query-origin load-surface invariant for scalar-page payload mismatch.
517    pub(crate) fn load_runtime_scalar_surface_payload_required() -> Self {
518        Self::query_executor_invariant("scalar page load mode must carry scalar runtime payload")
519    }
520
521    /// Construct a query-origin load-surface invariant for grouped-page payload mismatch.
522    pub(crate) fn load_runtime_grouped_surface_payload_required() -> Self {
523        Self::query_executor_invariant("grouped page load mode must carry grouped runtime payload")
524    }
525
526    /// Construct a query-origin load-entrypoint invariant for non-load plans.
527    pub(crate) fn load_executor_load_plan_required() -> Self {
528        Self::query_executor_invariant("load executor requires load plans")
529    }
530
531    /// Construct an executor-origin delete-entrypoint unsupported grouped-mode error.
532    pub(crate) fn delete_executor_grouped_unsupported() -> Self {
533        Self::executor_unsupported("grouped query execution is not yet enabled in this release")
534    }
535
536    /// Construct a query-origin delete-entrypoint invariant for non-delete plans.
537    pub(crate) fn delete_executor_delete_plan_required() -> Self {
538        Self::query_executor_invariant("delete executor requires delete plans")
539    }
540
541    /// Construct a query-origin aggregate kernel invariant for fold-mode contract drift.
542    pub(crate) fn aggregate_fold_mode_terminal_contract_required() -> Self {
543        Self::query_executor_invariant(
544            "aggregate fold mode must match route fold-mode contract for aggregate terminal",
545        )
546    }
547
548    /// Construct a query-origin fast-stream invariant for missing exact key-count observability.
549    pub(crate) fn fast_stream_exact_key_count_required() -> Self {
550        Self::query_executor_invariant("fast-path stream must expose an exact key-count hint")
551    }
552
553    /// Construct a query-origin fast-stream invariant for route kind/request mismatch.
554    pub(crate) fn fast_stream_route_kind_request_match_required() -> Self {
555        Self::query_executor_invariant("fast-stream route kind/request mismatch")
556    }
557
558    /// Construct a query-origin scan invariant for missing index-prefix executable specs.
559    pub(crate) fn secondary_index_prefix_spec_required() -> Self {
560        Self::query_executor_invariant(
561            "index-prefix executable spec must be materialized for index-prefix plans",
562        )
563    }
564
565    /// Construct a query-origin scan invariant for missing index-range executable specs.
566    pub(crate) fn index_range_limit_spec_required() -> Self {
567        Self::query_executor_invariant(
568            "index-range executable spec must be materialized for index-range plans",
569        )
570    }
571
572    /// Construct an executor-origin mutation unsupported error for duplicate atomic save keys.
573    pub(crate) fn mutation_atomic_save_duplicate_key(
574        entity_path: &str,
575        key: impl fmt::Display,
576    ) -> Self {
577        Self::executor_unsupported(format!(
578            "atomic save batch rejected duplicate key: entity={entity_path} key={key}",
579        ))
580    }
581
582    /// Construct an executor-origin mutation invariant for index-store generation drift.
583    pub(crate) fn mutation_index_store_generation_changed(
584        expected_generation: u64,
585        observed_generation: u64,
586    ) -> Self {
587        Self::executor_invariant(format!(
588            "index store generation changed between preflight and apply: expected {expected_generation}, found {observed_generation}",
589        ))
590    }
591
592    /// Build the canonical executor-invariant message prefix.
593    #[must_use]
594    #[cold]
595    #[inline(never)]
596    pub(crate) fn executor_invariant_message(reason: impl Into<String>) -> String {
597        format!("executor invariant violated: {}", reason.into())
598    }
599
600    /// Construct a planner-origin invariant violation.
601    #[cold]
602    #[inline(never)]
603    pub(crate) fn planner_invariant(message: impl Into<String>) -> Self {
604        Self::new(
605            ErrorClass::InvariantViolation,
606            ErrorOrigin::Planner,
607            message.into(),
608        )
609    }
610
611    /// Build the canonical invalid-logical-plan message prefix.
612    #[must_use]
613    pub(crate) fn invalid_logical_plan_message(reason: impl Into<String>) -> String {
614        format!("invalid logical plan: {}", reason.into())
615    }
616
617    /// Construct a planner-origin invariant with the canonical invalid-plan prefix.
618    pub(crate) fn query_invalid_logical_plan(reason: impl Into<String>) -> Self {
619        Self::planner_invariant(Self::invalid_logical_plan_message(reason))
620    }
621
622    /// Construct a query-origin invariant violation.
623    #[cold]
624    #[inline(never)]
625    pub(crate) fn query_invariant(message: impl Into<String>) -> Self {
626        Self::new(
627            ErrorClass::InvariantViolation,
628            ErrorOrigin::Query,
629            message.into(),
630        )
631    }
632
633    /// Construct a store-origin invariant violation.
634    pub(crate) fn store_invariant(message: impl Into<String>) -> Self {
635        Self::new(
636            ErrorClass::InvariantViolation,
637            ErrorOrigin::Store,
638            message.into(),
639        )
640    }
641
642    /// Construct the canonical duplicate runtime-hook entity-tag invariant.
643    pub(crate) fn duplicate_runtime_hooks_for_entity_tag(
644        entity_tag: crate::types::EntityTag,
645    ) -> Self {
646        Self::store_invariant(format!(
647            "duplicate runtime hooks for entity tag '{}'",
648            entity_tag.value()
649        ))
650    }
651
652    /// Construct the canonical duplicate runtime-hook entity-path invariant.
653    pub(crate) fn duplicate_runtime_hooks_for_entity_path(entity_path: &str) -> Self {
654        Self::store_invariant(format!(
655            "duplicate runtime hooks for entity path '{entity_path}'"
656        ))
657    }
658
659    /// Construct a store-origin internal error.
660    #[cold]
661    #[inline(never)]
662    pub(crate) fn store_internal(message: impl Into<String>) -> Self {
663        Self::new(ErrorClass::Internal, ErrorOrigin::Store, message.into())
664    }
665
666    /// Construct the canonical unconfigured commit-memory id internal error.
667    pub(crate) fn commit_memory_id_unconfigured() -> Self {
668        Self::store_internal(
669            "commit memory id is not configured; initialize recovery before commit store access",
670        )
671    }
672
673    /// Construct the canonical commit-memory id mismatch internal error.
674    pub(crate) fn commit_memory_id_mismatch(cached_id: u8, configured_id: u8) -> Self {
675        Self::store_internal(format!(
676            "commit memory id mismatch: cached={cached_id}, configured={configured_id}",
677        ))
678    }
679
680    /// Construct the canonical missing rollback-row invariant for delete execution.
681    pub(crate) fn delete_rollback_row_required() -> Self {
682        Self::store_internal("missing raw row for delete rollback")
683    }
684
685    /// Construct the canonical memory-registry initialization failure for commit memory.
686    pub(crate) fn commit_memory_registry_init_failed(err: impl fmt::Display) -> Self {
687        Self::store_internal(format!("memory registry init failed: {err}"))
688    }
689
690    /// Construct the canonical migration cursor persistence-width internal error.
691    pub(crate) fn migration_next_step_index_u64_required(id: &str, version: u64) -> Self {
692        Self::store_internal(format!(
693            "migration '{id}@{version}' next step index does not fit persisted u64 cursor",
694        ))
695    }
696
697    /// Construct the canonical recovery-integrity totals corruption error.
698    pub(crate) fn recovery_integrity_validation_failed(
699        missing_index_entries: u64,
700        divergent_index_entries: u64,
701        orphan_index_references: u64,
702    ) -> Self {
703        Self::store_corruption(format!(
704            "recovery integrity validation failed: missing_index_entries={missing_index_entries} divergent_index_entries={divergent_index_entries} orphan_index_references={orphan_index_references}",
705        ))
706    }
707
708    /// Construct an index-origin internal error.
709    #[cold]
710    #[inline(never)]
711    pub(crate) fn index_internal(message: impl Into<String>) -> Self {
712        Self::new(ErrorClass::Internal, ErrorOrigin::Index, message.into())
713    }
714
715    /// Construct the canonical missing old entity-key internal error for structural index removal.
716    pub(crate) fn structural_index_removal_entity_key_required() -> Self {
717        Self::index_internal("missing old entity key for structural index removal")
718    }
719
720    /// Construct the canonical missing new entity-key internal error for structural index insertion.
721    pub(crate) fn structural_index_insertion_entity_key_required() -> Self {
722        Self::index_internal("missing new entity key for structural index insertion")
723    }
724
725    /// Construct the canonical missing old entity-key internal error for index commit-op removal.
726    pub(crate) fn index_commit_op_old_entity_key_required() -> Self {
727        Self::index_internal("missing old entity key for index removal")
728    }
729
730    /// Construct the canonical missing new entity-key internal error for index commit-op insertion.
731    pub(crate) fn index_commit_op_new_entity_key_required() -> Self {
732        Self::index_internal("missing new entity key for index insertion")
733    }
734
735    /// Construct a query-origin internal error.
736    #[cfg(test)]
737    pub(crate) fn query_internal(message: impl Into<String>) -> Self {
738        Self::new(ErrorClass::Internal, ErrorOrigin::Query, message.into())
739    }
740
741    /// Construct a query-origin unsupported error.
742    #[cold]
743    #[inline(never)]
744    pub(crate) fn query_unsupported(message: impl Into<String>) -> Self {
745        Self::new(ErrorClass::Unsupported, ErrorOrigin::Query, message.into())
746    }
747
748    /// Construct a serialize-origin internal error.
749    #[cold]
750    #[inline(never)]
751    pub(crate) fn serialize_internal(message: impl Into<String>) -> Self {
752        Self::new(ErrorClass::Internal, ErrorOrigin::Serialize, message.into())
753    }
754
755    /// Construct the canonical persisted-row encode internal error.
756    pub(crate) fn persisted_row_encode_failed(detail: impl fmt::Display) -> Self {
757        Self::serialize_internal(format!("row encode failed: {detail}"))
758    }
759
760    /// Construct the canonical persisted-row field encode internal error.
761    pub(crate) fn persisted_row_field_encode_failed(
762        field_name: &str,
763        detail: impl fmt::Display,
764    ) -> Self {
765        Self::serialize_internal(format!(
766            "row encode failed for field '{field_name}': {detail}",
767        ))
768    }
769
770    /// Construct the canonical bytes(field) value encode internal error.
771    pub(crate) fn bytes_field_value_encode_failed(detail: impl fmt::Display) -> Self {
772        Self::serialize_internal(format!("bytes(field) value encode failed: {detail}"))
773    }
774
775    /// Construct the canonical migration-state serialization failure.
776    pub(crate) fn migration_state_serialize_failed(err: impl fmt::Display) -> Self {
777        Self::serialize_internal(format!("failed to serialize migration state: {err}"))
778    }
779
780    /// Construct a store-origin corruption error.
781    #[cold]
782    #[inline(never)]
783    pub(crate) fn store_corruption(message: impl Into<String>) -> Self {
784        Self::new(ErrorClass::Corruption, ErrorOrigin::Store, message.into())
785    }
786
787    /// Construct the canonical multiple-commit-memory-ids corruption error.
788    pub(crate) fn multiple_commit_memory_ids_registered(ids: impl fmt::Debug) -> Self {
789        Self::store_corruption(format!(
790            "multiple commit marker memory ids registered: {ids:?}"
791        ))
792    }
793
794    /// Construct the canonical persisted migration-step index conversion corruption error.
795    pub(crate) fn migration_persisted_step_index_invalid_usize(
796        id: &str,
797        version: u64,
798        step_index: u64,
799    ) -> Self {
800        Self::store_corruption(format!(
801            "migration '{id}@{version}' persisted step index does not fit runtime usize: {step_index}",
802        ))
803    }
804
805    /// Construct the canonical persisted migration-step index bounds corruption error.
806    pub(crate) fn migration_persisted_step_index_out_of_bounds(
807        id: &str,
808        version: u64,
809        step_index: usize,
810        total_steps: usize,
811    ) -> Self {
812        Self::store_corruption(format!(
813            "migration '{id}@{version}' persisted step index out of bounds: {step_index} > {total_steps}",
814        ))
815    }
816
817    /// Construct a store-origin commit-marker corruption error.
818    pub(crate) fn commit_corruption(detail: impl fmt::Display) -> Self {
819        Self::store_corruption(format!("commit marker corrupted: {detail}"))
820    }
821
822    /// Construct a store-origin commit-marker component corruption error.
823    pub(crate) fn commit_component_corruption(component: &str, detail: impl fmt::Display) -> Self {
824        Self::store_corruption(format!("commit marker {component} corrupted: {detail}"))
825    }
826
827    /// Construct the canonical commit-marker id generation internal error.
828    pub(crate) fn commit_id_generation_failed(detail: impl fmt::Display) -> Self {
829        Self::store_internal(format!("commit id generation failed: {detail}"))
830    }
831
832    /// Construct the canonical commit-marker payload u32-length-limit error.
833    pub(crate) fn commit_marker_payload_exceeds_u32_length_limit(label: &str, len: usize) -> Self {
834        Self::store_unsupported(format!("{label} exceeds u32 length limit: {len} bytes"))
835    }
836
837    /// Construct the canonical commit-marker component invalid-length corruption error.
838    pub(crate) fn commit_component_length_invalid(
839        component: &str,
840        len: usize,
841        expected: impl fmt::Display,
842    ) -> Self {
843        Self::commit_component_corruption(
844            component,
845            format!("invalid length {len}, expected {expected}"),
846        )
847    }
848
849    /// Construct the canonical commit-marker max-size corruption error.
850    pub(crate) fn commit_marker_exceeds_max_size(size: usize, max_size: u32) -> Self {
851        Self::commit_corruption(format!(
852            "commit marker exceeds max size: {size} bytes (limit {max_size})",
853        ))
854    }
855
856    /// Construct the canonical pre-persist commit-marker max-size unsupported error.
857    #[cfg(test)]
858    pub(crate) fn commit_marker_exceeds_max_size_before_persist(
859        size: usize,
860        max_size: u32,
861    ) -> Self {
862        Self::store_unsupported(format!(
863            "commit marker exceeds max size: {size} bytes (limit {max_size})",
864        ))
865    }
866
867    /// Construct the canonical commit-control slot max-size unsupported error.
868    pub(crate) fn commit_control_slot_exceeds_max_size(size: usize, max_size: u32) -> Self {
869        Self::store_unsupported(format!(
870            "commit control slot exceeds max size: {size} bytes (limit {max_size})",
871        ))
872    }
873
874    /// Construct the canonical commit-control marker-bytes length-limit error.
875    pub(crate) fn commit_control_slot_marker_bytes_exceed_u32_length_limit(size: usize) -> Self {
876        Self::store_unsupported(format!(
877            "commit marker bytes exceed u32 length limit: {size} bytes",
878        ))
879    }
880
881    /// Construct the canonical commit-control migration-bytes length-limit error.
882    pub(crate) fn commit_control_slot_migration_bytes_exceed_u32_length_limit(size: usize) -> Self {
883        Self::store_unsupported(format!(
884            "commit migration bytes exceed u32 length limit: {size} bytes",
885        ))
886    }
887
888    /// Construct the canonical startup index-rebuild invalid-data-key corruption error.
889    pub(crate) fn startup_index_rebuild_invalid_data_key(
890        store_path: &str,
891        detail: impl fmt::Display,
892    ) -> Self {
893        Self::store_corruption(format!(
894            "startup index rebuild failed: invalid data key in store '{store_path}' ({detail})",
895        ))
896    }
897
898    /// Construct an index-origin corruption error.
899    #[cold]
900    #[inline(never)]
901    pub(crate) fn index_corruption(message: impl Into<String>) -> Self {
902        Self::new(ErrorClass::Corruption, ErrorOrigin::Index, message.into())
903    }
904
905    /// Construct the canonical unique-validation corruption wrapper.
906    pub(crate) fn index_unique_validation_corruption(
907        entity_path: &str,
908        fields: &str,
909        detail: impl fmt::Display,
910    ) -> Self {
911        Self::index_plan_index_corruption(format!(
912            "index corrupted: {entity_path} ({fields}) -> {detail}",
913        ))
914    }
915
916    /// Construct the canonical structural index-entry corruption wrapper.
917    pub(crate) fn structural_index_entry_corruption(
918        entity_path: &str,
919        fields: &str,
920        detail: impl fmt::Display,
921    ) -> Self {
922        Self::index_plan_index_corruption(format!(
923            "index corrupted: {entity_path} ({fields}) -> {detail}",
924        ))
925    }
926
927    /// Construct the canonical missing new entity-key invariant during unique validation.
928    pub(crate) fn index_unique_validation_entity_key_required() -> Self {
929        Self::index_invariant("missing entity key during unique validation")
930    }
931
932    /// Construct the canonical unique-validation structural row-decode corruption error.
933    pub(crate) fn index_unique_validation_row_deserialize_failed(
934        data_key: impl fmt::Display,
935        source: impl fmt::Display,
936    ) -> Self {
937        Self::index_plan_serialize_corruption(format!(
938            "failed to structurally deserialize row: {data_key} ({source})"
939        ))
940    }
941
942    /// Construct the canonical unique-validation primary-key slot decode corruption error.
943    pub(crate) fn index_unique_validation_primary_key_decode_failed(
944        data_key: impl fmt::Display,
945        source: impl fmt::Display,
946    ) -> Self {
947        Self::index_plan_serialize_corruption(format!(
948            "failed to decode structural primary-key slot: {data_key} ({source})"
949        ))
950    }
951
952    /// Construct the canonical unique-validation stored key rebuild corruption error.
953    pub(crate) fn index_unique_validation_key_rebuild_failed(
954        data_key: impl fmt::Display,
955        entity_path: &str,
956        source: impl fmt::Display,
957    ) -> Self {
958        Self::index_plan_serialize_corruption(format!(
959            "failed to structurally decode unique key row {data_key} for {entity_path}: {source}",
960        ))
961    }
962
963    /// Construct the canonical unique-validation missing-row corruption error.
964    pub(crate) fn index_unique_validation_row_required(data_key: impl fmt::Display) -> Self {
965        Self::index_plan_store_corruption(format!("missing row: {data_key}"))
966    }
967
968    /// Construct the canonical index-only predicate missing-component invariant.
969    pub(crate) fn index_only_predicate_component_required() -> Self {
970        Self::index_invariant("index-only predicate program referenced missing index component")
971    }
972
973    /// Construct the canonical index-scan continuation-envelope invariant.
974    pub(crate) fn index_scan_continuation_anchor_within_envelope_required() -> Self {
975        Self::index_invariant(
976            "index-range continuation anchor is outside the requested range envelope",
977        )
978    }
979
980    /// Construct the canonical index-scan continuation-advancement invariant.
981    pub(crate) fn index_scan_continuation_advancement_required() -> Self {
982        Self::index_invariant("index-range continuation scan did not advance beyond the anchor")
983    }
984
985    /// Construct the canonical index-scan key-decode corruption error.
986    pub(crate) fn index_scan_key_corrupted_during(
987        context: &'static str,
988        err: impl fmt::Display,
989    ) -> Self {
990        Self::index_corruption(format!("index key corrupted during {context}: {err}"))
991    }
992
993    /// Construct the canonical index-scan missing projection-component invariant.
994    pub(crate) fn index_projection_component_required(
995        index_name: &str,
996        component_index: usize,
997    ) -> Self {
998        Self::index_invariant(format!(
999            "index projection referenced missing component: index='{index_name}' component_index={component_index}",
1000        ))
1001    }
1002
1003    /// Construct the canonical unexpected unique index-entry cardinality corruption error.
1004    pub(crate) fn unique_index_entry_single_key_required() -> Self {
1005        Self::index_corruption("unique index entry contains an unexpected number of keys")
1006    }
1007
1008    /// Construct the canonical scan-time index-entry decode corruption error.
1009    pub(crate) fn index_entry_decode_failed(err: impl fmt::Display) -> Self {
1010        Self::index_corruption(err.to_string())
1011    }
1012
1013    /// Construct a serialize-origin corruption error.
1014    pub(crate) fn serialize_corruption(message: impl Into<String>) -> Self {
1015        Self::new(
1016            ErrorClass::Corruption,
1017            ErrorOrigin::Serialize,
1018            message.into(),
1019        )
1020    }
1021
1022    /// Construct the canonical persisted-row decode corruption error.
1023    pub(crate) fn persisted_row_decode_failed(detail: impl fmt::Display) -> Self {
1024        Self::serialize_corruption(format!("row decode: {detail}"))
1025    }
1026
1027    /// Construct the canonical persisted-row field decode corruption error.
1028    pub(crate) fn persisted_row_field_decode_failed(
1029        field_name: &str,
1030        detail: impl fmt::Display,
1031    ) -> Self {
1032        Self::serialize_corruption(format!(
1033            "row decode failed for field '{field_name}': {detail}",
1034        ))
1035    }
1036
1037    /// Construct the canonical persisted-row field-kind decode corruption error.
1038    pub(crate) fn persisted_row_field_kind_decode_failed(
1039        field_name: &str,
1040        field_kind: impl fmt::Debug,
1041        detail: impl fmt::Display,
1042    ) -> Self {
1043        Self::persisted_row_field_decode_failed(
1044            field_name,
1045            format!("kind={field_kind:?}: {detail}"),
1046        )
1047    }
1048
1049    /// Construct the canonical persisted-row scalar-payload length corruption error.
1050    pub(crate) fn persisted_row_field_payload_exact_len_required(
1051        field_name: &str,
1052        payload_kind: &str,
1053        expected_len: usize,
1054    ) -> Self {
1055        let unit = if expected_len == 1 { "byte" } else { "bytes" };
1056
1057        Self::persisted_row_field_decode_failed(
1058            field_name,
1059            format!("{payload_kind} payload must be exactly {expected_len} {unit}"),
1060        )
1061    }
1062
1063    /// Construct the canonical persisted-row scalar-payload empty-body corruption error.
1064    pub(crate) fn persisted_row_field_payload_must_be_empty(
1065        field_name: &str,
1066        payload_kind: &str,
1067    ) -> Self {
1068        Self::persisted_row_field_decode_failed(
1069            field_name,
1070            format!("{payload_kind} payload must be empty"),
1071        )
1072    }
1073
1074    /// Construct the canonical persisted-row scalar-payload invalid-byte corruption error.
1075    pub(crate) fn persisted_row_field_payload_invalid_byte(
1076        field_name: &str,
1077        payload_kind: &str,
1078        value: u8,
1079    ) -> Self {
1080        Self::persisted_row_field_decode_failed(
1081            field_name,
1082            format!("invalid {payload_kind} payload byte {value}"),
1083        )
1084    }
1085
1086    /// Construct the canonical persisted-row scalar-payload non-finite corruption error.
1087    pub(crate) fn persisted_row_field_payload_non_finite(
1088        field_name: &str,
1089        payload_kind: &str,
1090    ) -> Self {
1091        Self::persisted_row_field_decode_failed(
1092            field_name,
1093            format!("{payload_kind} payload is non-finite"),
1094        )
1095    }
1096
1097    /// Construct the canonical persisted-row scalar-payload out-of-range corruption error.
1098    pub(crate) fn persisted_row_field_payload_out_of_range(
1099        field_name: &str,
1100        payload_kind: &str,
1101    ) -> Self {
1102        Self::persisted_row_field_decode_failed(
1103            field_name,
1104            format!("{payload_kind} payload out of range for target type"),
1105        )
1106    }
1107
1108    /// Construct the canonical persisted-row invalid text payload corruption error.
1109    pub(crate) fn persisted_row_field_text_payload_invalid_utf8(
1110        field_name: &str,
1111        detail: impl fmt::Display,
1112    ) -> Self {
1113        Self::persisted_row_field_decode_failed(
1114            field_name,
1115            format!("invalid UTF-8 text payload ({detail})"),
1116        )
1117    }
1118
1119    /// Construct the canonical persisted-row structural slot-lookup invariant.
1120    pub(crate) fn persisted_row_slot_lookup_out_of_bounds(model_path: &str, slot: usize) -> Self {
1121        Self::index_invariant(format!(
1122            "slot lookup outside model bounds during structural row access: model='{model_path}' slot={slot}",
1123        ))
1124    }
1125
1126    /// Construct the canonical persisted-row structural slot-cache invariant.
1127    pub(crate) fn persisted_row_slot_cache_lookup_out_of_bounds(
1128        model_path: &str,
1129        slot: usize,
1130    ) -> Self {
1131        Self::index_invariant(format!(
1132            "slot cache lookup outside model bounds during structural row access: model='{model_path}' slot={slot}",
1133        ))
1134    }
1135
1136    /// Construct the canonical persisted-row primary-key decode corruption error.
1137    pub(crate) fn persisted_row_primary_key_not_storage_encodable(
1138        data_key: impl fmt::Display,
1139        detail: impl fmt::Display,
1140    ) -> Self {
1141        Self::persisted_row_decode_failed(format!(
1142            "primary-key value is not storage-key encodable: {data_key} ({detail})",
1143        ))
1144    }
1145
1146    /// Construct the canonical persisted-row missing primary-key slot corruption error.
1147    pub(crate) fn persisted_row_primary_key_slot_missing(data_key: impl fmt::Display) -> Self {
1148        Self::persisted_row_decode_failed(format!(
1149            "missing primary-key slot while validating {data_key}",
1150        ))
1151    }
1152
1153    /// Construct the canonical persisted-row key mismatch corruption error.
1154    pub(crate) fn persisted_row_key_mismatch(
1155        expected_key: impl fmt::Display,
1156        found_key: impl fmt::Display,
1157    ) -> Self {
1158        Self::store_corruption(format!(
1159            "row key mismatch: expected {expected_key}, found {found_key}",
1160        ))
1161    }
1162
1163    /// Construct the canonical persisted-row missing declared-field corruption error.
1164    pub(crate) fn persisted_row_declared_field_missing(field_name: &str) -> Self {
1165        Self::persisted_row_decode_failed(format!("missing declared field `{field_name}`"))
1166    }
1167
1168    /// Construct the canonical data-key entity mismatch corruption error.
1169    pub(crate) fn data_key_entity_mismatch(
1170        expected: impl fmt::Display,
1171        found: impl fmt::Display,
1172    ) -> Self {
1173        Self::store_corruption(format!(
1174            "data key entity mismatch: expected {expected}, found {found}",
1175        ))
1176    }
1177
1178    /// Construct the canonical data-key primary-key decode corruption error.
1179    pub(crate) fn data_key_primary_key_decode_failed(value: impl fmt::Debug) -> Self {
1180        Self::store_corruption(format!("data key primary key decode failed: {value:?}",))
1181    }
1182
1183    /// Construct the canonical reverse-index ordinal overflow internal error.
1184    pub(crate) fn reverse_index_ordinal_overflow(
1185        source_path: &str,
1186        field_name: &str,
1187        target_path: &str,
1188        detail: impl fmt::Display,
1189    ) -> Self {
1190        Self::index_internal(format!(
1191            "reverse index ordinal overflow: source={source_path} field={field_name} target={target_path} ({detail})",
1192        ))
1193    }
1194
1195    /// Construct the canonical reverse-index entry corruption error.
1196    pub(crate) fn reverse_index_entry_corrupted(
1197        source_path: &str,
1198        field_name: &str,
1199        target_path: &str,
1200        index_key: impl fmt::Debug,
1201        detail: impl fmt::Display,
1202    ) -> Self {
1203        Self::index_corruption(format!(
1204            "reverse index entry corrupted: source={source_path} field={field_name} target={target_path} key={index_key:?} ({detail})",
1205        ))
1206    }
1207
1208    /// Construct the canonical reverse-index entry encode unsupported error.
1209    pub(crate) fn reverse_index_entry_encode_failed(
1210        source_path: &str,
1211        field_name: &str,
1212        target_path: &str,
1213        detail: impl fmt::Display,
1214    ) -> Self {
1215        Self::index_unsupported(format!(
1216            "reverse index entry encoding failed: source={source_path} field={field_name} target={target_path} ({detail})",
1217        ))
1218    }
1219
1220    /// Construct the canonical relation-target store missing internal error.
1221    pub(crate) fn relation_target_store_missing(
1222        source_path: &str,
1223        field_name: &str,
1224        target_path: &str,
1225        store_path: &str,
1226        detail: impl fmt::Display,
1227    ) -> Self {
1228        Self::executor_internal(format!(
1229            "relation target store missing: source={source_path} field={field_name} target={target_path} store={store_path} ({detail})",
1230        ))
1231    }
1232
1233    /// Construct the canonical relation-target key decode corruption error.
1234    pub(crate) fn relation_target_key_decode_failed(
1235        context_label: &str,
1236        source_path: &str,
1237        field_name: &str,
1238        target_path: &str,
1239        detail: impl fmt::Display,
1240    ) -> Self {
1241        Self::identity_corruption(format!(
1242            "{context_label}: source={source_path} field={field_name} target={target_path} ({detail})",
1243        ))
1244    }
1245
1246    /// Construct the canonical relation-target entity mismatch corruption error.
1247    pub(crate) fn relation_target_entity_mismatch(
1248        context_label: &str,
1249        source_path: &str,
1250        field_name: &str,
1251        target_path: &str,
1252        target_entity_name: &str,
1253        expected_tag: impl fmt::Display,
1254        actual_tag: impl fmt::Display,
1255    ) -> Self {
1256        Self::store_corruption(format!(
1257            "{context_label}: source={source_path} field={field_name} target={target_path} expected={target_entity_name} (tag={expected_tag}) actual_tag={actual_tag}",
1258        ))
1259    }
1260
1261    /// Construct the canonical relation-source row decode corruption error.
1262    pub(crate) fn relation_source_row_decode_failed(
1263        source_path: &str,
1264        field_name: &str,
1265        target_path: &str,
1266        detail: impl fmt::Display,
1267    ) -> Self {
1268        Self::serialize_corruption(format!(
1269            "relation source row decode: source={source_path} field={field_name} target={target_path} ({detail})",
1270        ))
1271    }
1272
1273    /// Construct the canonical relation-source unsupported scalar relation-key corruption error.
1274    pub(crate) fn relation_source_row_unsupported_scalar_relation_key(
1275        source_path: &str,
1276        field_name: &str,
1277        target_path: &str,
1278    ) -> Self {
1279        Self::serialize_corruption(format!(
1280            "relation source row decode: unsupported scalar relation key: source={source_path} field={field_name} target={target_path}",
1281        ))
1282    }
1283
1284    /// Construct the canonical invalid strong-relation field-kind corruption error.
1285    pub(crate) fn relation_source_row_invalid_field_kind(field_kind: impl fmt::Debug) -> Self {
1286        Self::serialize_corruption(format!(
1287            "invalid strong relation field kind during structural decode: {field_kind:?}"
1288        ))
1289    }
1290
1291    /// Construct the canonical unsupported strong-relation key-kind corruption error.
1292    pub(crate) fn relation_source_row_unsupported_key_kind(field_kind: impl fmt::Debug) -> Self {
1293        Self::serialize_corruption(format!(
1294            "unsupported strong relation key kind during structural decode: {field_kind:?}"
1295        ))
1296    }
1297
1298    /// Construct the canonical reverse-index relation-target decode invariant failure.
1299    pub(crate) fn reverse_index_relation_target_decode_invariant_violated(
1300        source_path: &str,
1301        field_name: &str,
1302        target_path: &str,
1303    ) -> Self {
1304        Self::executor_internal(format!(
1305            "relation target decode invariant violated while preparing reverse index: source={source_path} field={field_name} target={target_path}",
1306        ))
1307    }
1308
1309    /// Construct the canonical covering-component empty-payload corruption error.
1310    pub(crate) fn bytes_covering_component_payload_empty() -> Self {
1311        Self::index_corruption("index component payload is empty during covering projection decode")
1312    }
1313
1314    /// Construct the canonical covering-component truncated bool corruption error.
1315    pub(crate) fn bytes_covering_bool_payload_truncated() -> Self {
1316        Self::index_corruption("bool covering component payload is truncated")
1317    }
1318
1319    /// Construct the canonical covering-component invalid-length corruption error.
1320    pub(crate) fn bytes_covering_component_payload_invalid_length(payload_kind: &str) -> Self {
1321        Self::index_corruption(format!(
1322            "{payload_kind} covering component payload has invalid length"
1323        ))
1324    }
1325
1326    /// Construct the canonical covering-component invalid-bool corruption error.
1327    pub(crate) fn bytes_covering_bool_payload_invalid_value() -> Self {
1328        Self::index_corruption("bool covering component payload has invalid value")
1329    }
1330
1331    /// Construct the canonical covering-component invalid text terminator corruption error.
1332    pub(crate) fn bytes_covering_text_payload_invalid_terminator() -> Self {
1333        Self::index_corruption("text covering component payload has invalid terminator")
1334    }
1335
1336    /// Construct the canonical covering-component trailing-text corruption error.
1337    pub(crate) fn bytes_covering_text_payload_trailing_bytes() -> Self {
1338        Self::index_corruption("text covering component payload contains trailing bytes")
1339    }
1340
1341    /// Construct the canonical covering-component invalid-UTF-8 text corruption error.
1342    pub(crate) fn bytes_covering_text_payload_invalid_utf8() -> Self {
1343        Self::index_corruption("text covering component payload is not valid UTF-8")
1344    }
1345
1346    /// Construct the canonical covering-component invalid text escape corruption error.
1347    pub(crate) fn bytes_covering_text_payload_invalid_escape_byte() -> Self {
1348        Self::index_corruption("text covering component payload has invalid escape byte")
1349    }
1350
1351    /// Construct the canonical covering-component missing text terminator corruption error.
1352    pub(crate) fn bytes_covering_text_payload_missing_terminator() -> Self {
1353        Self::index_corruption("text covering component payload is missing terminator")
1354    }
1355
1356    /// Construct the canonical missing persisted-field decode error.
1357    #[must_use]
1358    pub fn missing_persisted_slot(field_name: &'static str) -> Self {
1359        Self::serialize_corruption(format!("row decode: missing required field '{field_name}'",))
1360    }
1361
1362    /// Construct an identity-origin corruption error.
1363    pub(crate) fn identity_corruption(message: impl Into<String>) -> Self {
1364        Self::new(
1365            ErrorClass::Corruption,
1366            ErrorOrigin::Identity,
1367            message.into(),
1368        )
1369    }
1370
1371    /// Construct a store-origin unsupported error.
1372    #[cold]
1373    #[inline(never)]
1374    pub(crate) fn store_unsupported(message: impl Into<String>) -> Self {
1375        Self::new(ErrorClass::Unsupported, ErrorOrigin::Store, message.into())
1376    }
1377
1378    /// Construct the canonical empty migration label unsupported error.
1379    pub(crate) fn migration_label_empty(label: &str) -> Self {
1380        Self::store_unsupported(format!("{label} cannot be empty"))
1381    }
1382
1383    /// Construct the canonical empty migration-step row-op set unsupported error.
1384    pub(crate) fn migration_step_row_ops_required(name: &str) -> Self {
1385        Self::store_unsupported(format!(
1386            "migration step '{name}' must include at least one row op",
1387        ))
1388    }
1389
1390    /// Construct the canonical invalid migration-plan version unsupported error.
1391    pub(crate) fn migration_plan_version_required(id: &str) -> Self {
1392        Self::store_unsupported(format!("migration plan '{id}' version must be > 0",))
1393    }
1394
1395    /// Construct the canonical empty migration-plan steps unsupported error.
1396    pub(crate) fn migration_plan_steps_required(id: &str) -> Self {
1397        Self::store_unsupported(format!(
1398            "migration plan '{id}' must include at least one step",
1399        ))
1400    }
1401
1402    /// Construct the canonical migration cursor out-of-bounds unsupported error.
1403    pub(crate) fn migration_cursor_out_of_bounds(
1404        id: &str,
1405        version: u64,
1406        next_step: usize,
1407        total_steps: usize,
1408    ) -> Self {
1409        Self::store_unsupported(format!(
1410            "migration '{id}@{version}' cursor out of bounds: next_step={next_step} total_steps={total_steps}",
1411        ))
1412    }
1413
1414    /// Construct the canonical max-steps-required migration execution error.
1415    pub(crate) fn migration_execution_requires_max_steps(id: &str) -> Self {
1416        Self::store_unsupported(format!("migration '{id}' execution requires max_steps > 0",))
1417    }
1418
1419    /// Construct the canonical in-progress migration-plan conflict error.
1420    pub(crate) fn migration_in_progress_conflict(
1421        requested_id: &str,
1422        requested_version: u64,
1423        active_id: &str,
1424        active_version: u64,
1425    ) -> Self {
1426        Self::store_unsupported(format!(
1427            "migration '{requested_id}@{requested_version}' cannot execute while migration '{active_id}@{active_version}' is in progress",
1428        ))
1429    }
1430
1431    /// Construct the canonical unsupported persisted entity-tag store error.
1432    pub(crate) fn unsupported_entity_tag_in_data_store(
1433        entity_tag: crate::types::EntityTag,
1434    ) -> Self {
1435        Self::store_unsupported(format!(
1436            "unsupported entity tag in data store: '{}'",
1437            entity_tag.value()
1438        ))
1439    }
1440
1441    /// Construct the canonical configured-vs-registered commit-memory id mismatch error.
1442    pub(crate) fn configured_commit_memory_id_mismatch(
1443        configured_id: u8,
1444        registered_id: u8,
1445    ) -> Self {
1446        Self::store_unsupported(format!(
1447            "configured commit memory id {configured_id} does not match existing commit marker id {registered_id}",
1448        ))
1449    }
1450
1451    /// Construct the canonical occupied commit-memory id unsupported error.
1452    pub(crate) fn commit_memory_id_already_registered(memory_id: u8, label: &str) -> Self {
1453        Self::store_unsupported(format!(
1454            "configured commit memory id {memory_id} is already registered as '{label}'",
1455        ))
1456    }
1457
1458    /// Construct the canonical out-of-range commit-memory id unsupported error.
1459    pub(crate) fn commit_memory_id_outside_reserved_ranges(memory_id: u8) -> Self {
1460        Self::store_unsupported(format!(
1461            "configured commit memory id {memory_id} is outside reserved ranges",
1462        ))
1463    }
1464
1465    /// Construct the canonical commit-memory id registration failure.
1466    pub(crate) fn commit_memory_id_registration_failed(err: impl fmt::Display) -> Self {
1467        Self::store_internal(format!("commit memory id registration failed: {err}"))
1468    }
1469
1470    /// Construct an index-origin unsupported error.
1471    pub(crate) fn index_unsupported(message: impl Into<String>) -> Self {
1472        Self::new(ErrorClass::Unsupported, ErrorOrigin::Index, message.into())
1473    }
1474
1475    /// Construct the canonical index-key component size-limit unsupported error.
1476    pub(crate) fn index_component_exceeds_max_size(
1477        key_item: impl fmt::Display,
1478        len: usize,
1479        max_component_size: usize,
1480    ) -> Self {
1481        Self::index_unsupported(format!(
1482            "index component exceeds max size: key item '{key_item}' -> {len} bytes (limit {max_component_size})",
1483        ))
1484    }
1485
1486    /// Construct the canonical index-entry max-keys unsupported error during commit encoding.
1487    pub(crate) fn index_entry_exceeds_max_keys(
1488        entity_path: &str,
1489        fields: &str,
1490        keys: usize,
1491    ) -> Self {
1492        Self::index_unsupported(format!(
1493            "index entry exceeds max keys: {entity_path} ({fields}) -> {keys} keys",
1494        ))
1495    }
1496
1497    /// Construct the canonical duplicate-key invariant during commit entry encoding.
1498    #[cfg(test)]
1499    pub(crate) fn index_entry_duplicate_keys_unexpected(entity_path: &str, fields: &str) -> Self {
1500        Self::index_invariant(format!(
1501            "index entry unexpectedly contains duplicate keys: {entity_path} ({fields})",
1502        ))
1503    }
1504
1505    /// Construct the canonical index-entry key-encoding unsupported error during commit encoding.
1506    pub(crate) fn index_entry_key_encoding_failed(
1507        entity_path: &str,
1508        fields: &str,
1509        err: impl fmt::Display,
1510    ) -> Self {
1511        Self::index_unsupported(format!(
1512            "index entry key encoding failed: {entity_path} ({fields}) -> {err}",
1513        ))
1514    }
1515
1516    /// Construct a serialize-origin unsupported error.
1517    pub(crate) fn serialize_unsupported(message: impl Into<String>) -> Self {
1518        Self::new(
1519            ErrorClass::Unsupported,
1520            ErrorOrigin::Serialize,
1521            message.into(),
1522        )
1523    }
1524
1525    /// Construct a cursor-origin unsupported error.
1526    pub(crate) fn cursor_unsupported(message: impl Into<String>) -> Self {
1527        Self::new(ErrorClass::Unsupported, ErrorOrigin::Cursor, message.into())
1528    }
1529
1530    /// Construct a serialize-origin incompatible persisted-format error.
1531    pub(crate) fn serialize_incompatible_persisted_format(message: impl Into<String>) -> Self {
1532        Self::new(
1533            ErrorClass::IncompatiblePersistedFormat,
1534            ErrorOrigin::Serialize,
1535            message.into(),
1536        )
1537    }
1538
1539    /// Construct the canonical persisted-payload decode failure mapping for one
1540    /// DB-owned serialized payload boundary.
1541    pub(crate) fn serialize_payload_decode_failed(
1542        source: SerializeError,
1543        payload_label: &'static str,
1544    ) -> Self {
1545        match source {
1546            // DB codec only decodes engine-owned persisted payloads.
1547            // Size-limit breaches indicate persisted bytes violate DB storage policy.
1548            SerializeError::DeserializeSizeLimitExceeded { len, max_bytes } => {
1549                Self::serialize_corruption(format!(
1550                    "{payload_label} decode failed: payload size {len} exceeds limit {max_bytes}"
1551                ))
1552            }
1553            SerializeError::Deserialize(_) => Self::serialize_corruption(format!(
1554                "{payload_label} decode failed: {}",
1555                SerializeErrorKind::Deserialize
1556            )),
1557            SerializeError::Serialize(_) => Self::serialize_corruption(format!(
1558                "{payload_label} decode failed: {}",
1559                SerializeErrorKind::Serialize
1560            )),
1561        }
1562    }
1563
1564    /// Construct a query-origin unsupported error preserving one SQL parser
1565    /// unsupported-feature label in structured error detail.
1566    #[cfg(feature = "sql")]
1567    pub(crate) fn query_unsupported_sql_feature(feature: &'static str) -> Self {
1568        let message = format!(
1569            "SQL query is not executable in this release: unsupported SQL feature: {feature}"
1570        );
1571
1572        Self {
1573            class: ErrorClass::Unsupported,
1574            origin: ErrorOrigin::Query,
1575            message,
1576            detail: Some(ErrorDetail::Query(
1577                QueryErrorDetail::UnsupportedSqlFeature { feature },
1578            )),
1579        }
1580    }
1581
1582    pub fn store_not_found(key: impl Into<String>) -> Self {
1583        let key = key.into();
1584
1585        Self {
1586            class: ErrorClass::NotFound,
1587            origin: ErrorOrigin::Store,
1588            message: format!("data key not found: {key}"),
1589            detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
1590        }
1591    }
1592
1593    /// Construct a standardized unsupported-entity-path error.
1594    pub fn unsupported_entity_path(path: impl Into<String>) -> Self {
1595        let path = path.into();
1596
1597        Self::new(
1598            ErrorClass::Unsupported,
1599            ErrorOrigin::Store,
1600            format!("unsupported entity path: '{path}'"),
1601        )
1602    }
1603
1604    #[must_use]
1605    pub const fn is_not_found(&self) -> bool {
1606        matches!(
1607            self.detail,
1608            Some(ErrorDetail::Store(StoreError::NotFound { .. }))
1609        )
1610    }
1611
1612    #[must_use]
1613    pub fn display_with_class(&self) -> String {
1614        format!("{}:{}: {}", self.origin, self.class, self.message)
1615    }
1616
1617    /// Construct an index-plan corruption error with a canonical prefix.
1618    #[cold]
1619    #[inline(never)]
1620    pub(crate) fn index_plan_corruption(origin: ErrorOrigin, message: impl Into<String>) -> Self {
1621        let message = message.into();
1622        Self::new(
1623            ErrorClass::Corruption,
1624            origin,
1625            format!("corruption detected ({origin}): {message}"),
1626        )
1627    }
1628
1629    /// Construct an index-plan corruption error for index-origin failures.
1630    #[cold]
1631    #[inline(never)]
1632    pub(crate) fn index_plan_index_corruption(message: impl Into<String>) -> Self {
1633        Self::index_plan_corruption(ErrorOrigin::Index, message)
1634    }
1635
1636    /// Construct an index-plan corruption error for store-origin failures.
1637    #[cold]
1638    #[inline(never)]
1639    pub(crate) fn index_plan_store_corruption(message: impl Into<String>) -> Self {
1640        Self::index_plan_corruption(ErrorOrigin::Store, message)
1641    }
1642
1643    /// Construct an index-plan corruption error for serialize-origin failures.
1644    #[cold]
1645    #[inline(never)]
1646    pub(crate) fn index_plan_serialize_corruption(message: impl Into<String>) -> Self {
1647        Self::index_plan_corruption(ErrorOrigin::Serialize, message)
1648    }
1649
1650    /// Construct an index-plan invariant violation error with a canonical prefix.
1651    #[cfg(test)]
1652    pub(crate) fn index_plan_invariant(origin: ErrorOrigin, message: impl Into<String>) -> Self {
1653        let message = message.into();
1654        Self::new(
1655            ErrorClass::InvariantViolation,
1656            origin,
1657            format!("invariant violation detected ({origin}): {message}"),
1658        )
1659    }
1660
1661    /// Construct an index-plan invariant violation error for store-origin failures.
1662    #[cfg(test)]
1663    pub(crate) fn index_plan_store_invariant(message: impl Into<String>) -> Self {
1664        Self::index_plan_invariant(ErrorOrigin::Store, message)
1665    }
1666
1667    /// Construct an index uniqueness violation conflict error.
1668    pub(crate) fn index_violation(path: &str, index_fields: &[&str]) -> Self {
1669        Self::new(
1670            ErrorClass::Conflict,
1671            ErrorOrigin::Index,
1672            format!(
1673                "index constraint violation: {path} ({})",
1674                index_fields.join(", ")
1675            ),
1676        )
1677    }
1678}
1679
1680///
1681/// ErrorDetail
1682///
1683/// Structured, origin-specific error detail carried by [`InternalError`].
1684/// This enum is intentionally extensible.
1685///
1686
1687#[derive(Debug, ThisError)]
1688pub enum ErrorDetail {
1689    #[error("{0}")]
1690    Store(StoreError),
1691    #[error("{0}")]
1692    Query(QueryErrorDetail),
1693    // Future-proofing:
1694    // #[error("{0}")]
1695    // Index(IndexError),
1696    //
1697    // #[error("{0}")]
1698    // Executor(ExecutorErrorDetail),
1699}
1700
1701///
1702/// StoreError
1703///
1704/// Store-specific structured error detail.
1705/// Never returned directly; always wrapped in [`ErrorDetail::Store`].
1706///
1707
1708#[derive(Debug, ThisError)]
1709pub enum StoreError {
1710    #[error("key not found: {key}")]
1711    NotFound { key: String },
1712
1713    #[error("store corruption: {message}")]
1714    Corrupt { message: String },
1715
1716    #[error("store invariant violation: {message}")]
1717    InvariantViolation { message: String },
1718}
1719
1720///
1721/// QueryErrorDetail
1722///
1723/// Query-origin structured error detail payload.
1724///
1725
1726#[derive(Debug, ThisError)]
1727pub enum QueryErrorDetail {
1728    #[error("unsupported SQL feature: {feature}")]
1729    UnsupportedSqlFeature { feature: &'static str },
1730}
1731
1732///
1733/// ErrorClass
1734/// Internal error taxonomy for runtime classification.
1735/// Not a stable API; may change without notice.
1736///
1737
1738#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1739pub enum ErrorClass {
1740    Corruption,
1741    IncompatiblePersistedFormat,
1742    NotFound,
1743    Internal,
1744    Conflict,
1745    Unsupported,
1746    InvariantViolation,
1747}
1748
1749impl fmt::Display for ErrorClass {
1750    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1751        let label = match self {
1752            Self::Corruption => "corruption",
1753            Self::IncompatiblePersistedFormat => "incompatible_persisted_format",
1754            Self::NotFound => "not_found",
1755            Self::Internal => "internal",
1756            Self::Conflict => "conflict",
1757            Self::Unsupported => "unsupported",
1758            Self::InvariantViolation => "invariant_violation",
1759        };
1760        write!(f, "{label}")
1761    }
1762}
1763
1764///
1765/// ErrorOrigin
1766/// Internal origin taxonomy for runtime classification.
1767/// Not a stable API; may change without notice.
1768///
1769
1770#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1771pub enum ErrorOrigin {
1772    Serialize,
1773    Store,
1774    Index,
1775    Identity,
1776    Query,
1777    Planner,
1778    Cursor,
1779    Recovery,
1780    Response,
1781    Executor,
1782    Interface,
1783}
1784
1785impl fmt::Display for ErrorOrigin {
1786    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1787        let label = match self {
1788            Self::Serialize => "serialize",
1789            Self::Store => "store",
1790            Self::Index => "index",
1791            Self::Identity => "identity",
1792            Self::Query => "query",
1793            Self::Planner => "planner",
1794            Self::Cursor => "cursor",
1795            Self::Recovery => "recovery",
1796            Self::Response => "response",
1797            Self::Executor => "executor",
1798            Self::Interface => "interface",
1799        };
1800        write!(f, "{label}")
1801    }
1802}