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 candid::CandidType;
12use icydb_diagnostic_code as diagnostic_code;
13use serde::Deserialize;
14use std::fmt;
15
16pub(crate) const COMPACT_QUERY_DIAGNOSTIC_MESSAGE: &str = "query diagnostic";
17const COMPACT_RUNTIME_DIAGNOSTIC_MESSAGE: &str = "runtime diagnostic";
18const COMPACT_STORE_DIAGNOSTIC_MESSAGE: &str = "store diagnostic";
19const COMPACT_INDEX_DIAGNOSTIC_MESSAGE: &str = "index diagnostic";
20const COMPACT_SERIALIZE_DIAGNOSTIC_MESSAGE: &str = "serialize diagnostic";
21const COMPACT_IDENTITY_DIAGNOSTIC_MESSAGE: &str = "identity diagnostic";
22
23const fn compact_message_for(_class: ErrorClass, origin: ErrorOrigin) -> &'static str {
24    match origin {
25        ErrorOrigin::Serialize => COMPACT_SERIALIZE_DIAGNOSTIC_MESSAGE,
26        ErrorOrigin::Store => COMPACT_STORE_DIAGNOSTIC_MESSAGE,
27        ErrorOrigin::Index => COMPACT_INDEX_DIAGNOSTIC_MESSAGE,
28        ErrorOrigin::Identity => COMPACT_IDENTITY_DIAGNOSTIC_MESSAGE,
29        ErrorOrigin::Query | ErrorOrigin::Planner | ErrorOrigin::Response => {
30            COMPACT_QUERY_DIAGNOSTIC_MESSAGE
31        }
32        ErrorOrigin::Cursor
33        | ErrorOrigin::Recovery
34        | ErrorOrigin::Executor
35        | ErrorOrigin::Interface => COMPACT_RUNTIME_DIAGNOSTIC_MESSAGE,
36    }
37}
38
39// ============================================================================
40// INTERNAL ERROR TAXONOMY — ARCHITECTURAL CONTRACT
41// ============================================================================
42//
43// This file defines the canonical runtime error classification system for
44// icydb-core. It is the single source of truth for:
45//
46//   • ErrorClass   (semantic domain)
47//   • ErrorOrigin  (subsystem boundary)
48//   • Structured detail payloads
49//   • Canonical constructor entry points
50//
51// -----------------------------------------------------------------------------
52// DESIGN INTENT
53// -----------------------------------------------------------------------------
54//
55// 1. InternalError is a *taxonomy carrier*, not a formatting utility.
56//
57//    - ErrorClass represents semantic meaning (corruption, invariant_violation,
58//      unsupported, etc).
59//    - ErrorOrigin represents the subsystem boundary (store, index, query,
60//      executor, serialize, interface, etc).
61//    - The (class, origin) pair must remain stable and intentional.
62//
63// 2. Call sites MUST prefer canonical constructors.
64//
65//    Do NOT construct errors manually via:
66//        InternalError::new(class, origin)
67//    unless you are defining a new canonical helper here.
68//
69//    If a pattern appears more than once, centralize it here.
70//
71// 3. Constructors in this file must represent real architectural boundaries.
72//
73//    Add a new helper ONLY if it:
74//
75//      • Encodes a cross-cutting invariant,
76//      • Represents a subsystem boundary,
77//      • Or prevents taxonomy drift across call sites.
78//
79//    Do NOT add feature-specific helpers.
80//    Do NOT add one-off formatting helpers.
81//    Do NOT turn this file into a generic message factory.
82//
83// 4. ErrorDetail must align with ErrorOrigin.
84//
85//    If detail is present, it MUST correspond to the origin.
86//    Do not attach mismatched detail variants.
87//
88// 5. Plan-layer errors are NOT runtime failures.
89//
90//    PlanError and CursorPlanError must be translated into
91//    executor/query invariants via the canonical mapping functions.
92//    Do not leak plan-layer error types across execution boundaries.
93//
94// 6. Preserve taxonomy stability.
95//
96//    Do NOT:
97//      • Merge error classes.
98//      • Reclassify corruption as internal.
99//      • Downgrade invariant violations.
100//      • Introduce ambiguous class/origin combinations.
101//
102//    Any change to ErrorClass or ErrorOrigin is an architectural change
103//    and must be reviewed accordingly.
104//
105// -----------------------------------------------------------------------------
106// NON-GOALS
107// -----------------------------------------------------------------------------
108//
109// This is NOT:
110//
111//   • A public API contract.
112//   • A generic error abstraction layer.
113//   • A feature-specific message builder.
114//   • A dumping ground for temporary error conversions.
115//
116// -----------------------------------------------------------------------------
117// MAINTENANCE GUIDELINES
118// -----------------------------------------------------------------------------
119//
120// When modifying this file:
121//
122//   1. Ensure classification semantics remain consistent.
123//   2. Avoid constructor proliferation.
124//   3. Prefer narrow, origin-specific helpers over ad-hoc new(...).
125//   4. Keep formatting minimal and standardized.
126//   5. Keep this file boring and stable.
127//
128// If this file grows rapidly, something is wrong at the call sites.
129//
130// ============================================================================
131
132/// Safe accepted mutation identity retained only when constructing a failure.
133#[derive(Clone, Copy, Debug)]
134pub(crate) struct MutationDiagnosticContext {
135    entity_tag: u64,
136    operation: diagnostic_code::DiagnosticMutationOperation,
137    batch_position: Option<u32>,
138}
139
140impl MutationDiagnosticContext {
141    /// Bind one mutation failure to its accepted entity, operation, and input.
142    #[must_use]
143    pub(crate) const fn new(
144        entity_tag: u64,
145        operation: diagnostic_code::DiagnosticMutationOperation,
146        batch_position: u32,
147    ) -> Self {
148        Self {
149            entity_tag,
150            operation,
151            batch_position: Some(batch_position),
152        }
153    }
154
155    /// Bind a failure to an operation before any concrete input row is selected.
156    #[must_use]
157    pub(crate) const fn operation_only(
158        entity_tag: u64,
159        operation: diagnostic_code::DiagnosticMutationOperation,
160    ) -> Self {
161        Self {
162            entity_tag,
163            operation,
164            batch_position: None,
165        }
166    }
167
168    fn facts(self, field_id: Option<u32>) -> Vec<(diagnostic_code::DiagnosticFactTag, u64)> {
169        let mut facts = Vec::with_capacity(
170            2 + usize::from(field_id.is_some()) + usize::from(self.batch_position.is_some()),
171        );
172        facts.push((
173            diagnostic_code::DiagnosticFactTag::EntityTag,
174            self.entity_tag,
175        ));
176        if let Some(field_id) = field_id {
177            facts.push((
178                diagnostic_code::DiagnosticFactTag::FieldId,
179                u64::from(field_id),
180            ));
181        }
182        facts.push((
183            diagnostic_code::DiagnosticFactTag::MutationOperation,
184            self.operation.raw(),
185        ));
186        if let Some(batch_position) = self.batch_position {
187            facts.push((
188                diagnostic_code::DiagnosticFactTag::BatchPosition,
189                u64::from(batch_position),
190            ));
191        }
192        facts
193    }
194
195    #[must_use]
196    pub(crate) const fn entity_tag(self) -> u64 {
197        self.entity_tag
198    }
199
200    fn append_operation_facts(self, facts: &mut Vec<(diagnostic_code::DiagnosticFactTag, u64)>) {
201        facts.push((
202            diagnostic_code::DiagnosticFactTag::MutationOperation,
203            self.operation.raw(),
204        ));
205        if let Some(batch_position) = self.batch_position {
206            facts.push((
207                diagnostic_code::DiagnosticFactTag::BatchPosition,
208                u64::from(batch_position),
209            ));
210        }
211    }
212}
213
214/// Numeric context retained behind one thin error-only allocation.
215pub struct DiagnosticFactDetail {
216    diagnostic: diagnostic_code::Diagnostic,
217    facts: Vec<(diagnostic_code::DiagnosticFactTag, u64)>,
218}
219
220///
221/// InternalError
222///
223/// Structured runtime error with a stable internal classification.
224/// Not a stable API; intended for internal use and may change without notice.
225///
226
227pub struct InternalError {
228    pub(crate) class: ErrorClass,
229    pub(crate) origin: ErrorOrigin,
230
231    /// Optional structured error detail.
232    /// The variant (if present) must correspond to `origin`.
233    pub(crate) detail: Option<ErrorDetail>,
234}
235
236#[expect(
237    clippy::missing_const_for_fn,
238    reason = "internal error constructors stay non-const so compact diagnostic construction does not force const churn across subsystem helper seams"
239)]
240impl InternalError {
241    /// Construct an InternalError with optional origin-specific detail.
242    /// This constructor provides default StoreError details for certain
243    /// (class, origin) combinations but does not guarantee a detail payload.
244    #[must_use]
245    #[cold]
246    #[inline(never)]
247    pub fn new(class: ErrorClass, origin: ErrorOrigin) -> Self {
248        let detail = match (class, origin) {
249            (ErrorClass::Corruption, ErrorOrigin::Store) => {
250                Some(ErrorDetail::Store(StoreError::Corrupt))
251            }
252            (ErrorClass::InvariantViolation, ErrorOrigin::Store) => {
253                Some(ErrorDetail::Store(StoreError::InvariantViolation))
254            }
255            _ => None,
256        };
257
258        Self {
259            class,
260            origin,
261            detail,
262        }
263    }
264
265    /// Return the internal error class taxonomy.
266    #[must_use]
267    pub const fn class(&self) -> ErrorClass {
268        self.class
269    }
270
271    /// Return the internal error origin taxonomy.
272    #[must_use]
273    pub const fn origin(&self) -> ErrorOrigin {
274        self.origin
275    }
276
277    /// Return the rendered internal error message.
278    #[must_use]
279    pub const fn message(&self) -> &'static str {
280        compact_message_for(self.class, self.origin)
281    }
282
283    /// Return the optional structured detail payload.
284    #[must_use]
285    pub const fn detail(&self) -> Option<&ErrorDetail> {
286        self.detail.as_ref()
287    }
288
289    /// Return compact diagnostic identity for this internal error.
290    #[must_use]
291    pub fn diagnostic(&self) -> diagnostic_code::Diagnostic {
292        diagnostic_code::Diagnostic::new(
293            self.diagnostic_code(),
294            self.origin.diagnostic_origin(),
295            self.detail
296                .as_ref()
297                .and_then(ErrorDetail::diagnostic_detail),
298        )
299    }
300
301    /// Project typed internal context into canonical public numeric facts.
302    #[must_use]
303    #[cold]
304    #[inline(never)]
305    pub fn diagnostic_facts(&self) -> Vec<(diagnostic_code::DiagnosticFactTag, u64)> {
306        self.detail
307            .as_ref()
308            .map_or_else(Vec::new, ErrorDetail::diagnostic_facts)
309    }
310
311    /// Return the compact diagnostic code for this internal error.
312    #[must_use]
313    pub fn diagnostic_code(&self) -> diagnostic_code::DiagnosticCode {
314        self.detail.as_ref().map_or_else(
315            || self.class.diagnostic_code(self.origin),
316            ErrorDetail::diagnostic_code,
317        )
318    }
319
320    /// Consume and return the rendered internal error message.
321    #[must_use]
322    pub fn into_message(self) -> String {
323        self.message().to_string()
324    }
325
326    /// Construct an error while preserving an explicit class/origin taxonomy pair.
327    #[cold]
328    #[inline(never)]
329    pub(crate) fn classified(class: ErrorClass, origin: ErrorOrigin) -> Self {
330        Self::new(class, origin)
331    }
332
333    #[cold]
334    #[inline(never)]
335    fn with_diagnostic_facts(
336        class: ErrorClass,
337        origin: ErrorOrigin,
338        detail: Option<diagnostic_code::DiagnosticDetail>,
339        facts: Vec<(diagnostic_code::DiagnosticFactTag, u64)>,
340    ) -> Self {
341        let code = match detail {
342            Some(detail) => detail.diagnostic_code(),
343            None => class.diagnostic_code(origin),
344        };
345        let diagnostic = diagnostic_code::Diagnostic::new(code, origin.diagnostic_origin(), detail);
346        if diagnostic_code::validate_known_diagnostic_fact_schema(
347            diagnostic.error_code(),
348            facts.as_slice(),
349        )
350        .is_err()
351        {
352            return Self::new(ErrorClass::InvariantViolation, origin);
353        }
354        Self {
355            class,
356            origin,
357            detail: Some(ErrorDetail::DiagnosticFacts(Box::new(
358                DiagnosticFactDetail { diagnostic, facts },
359            ))),
360        }
361    }
362
363    #[cold]
364    #[inline(never)]
365    fn mutation_boundary_with_facts(
366        class: ErrorClass,
367        boundary: diagnostic_code::RuntimeBoundaryCode,
368        facts: Vec<(diagnostic_code::DiagnosticFactTag, u64)>,
369    ) -> Self {
370        Self::with_diagnostic_facts(
371            class,
372            ErrorOrigin::Executor,
373            Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary { boundary }),
374            facts,
375        )
376    }
377
378    /// Rebuild this error with a new origin while preserving class taxonomy.
379    ///
380    /// Numeric facts are origin-independent and remain safe after recovery
381    /// relabeling. Other origin-scoped detail payloads are dropped.
382    #[cold]
383    #[inline(never)]
384    pub(crate) fn with_origin(self, origin: ErrorOrigin) -> Self {
385        match self.detail {
386            Some(ErrorDetail::DiagnosticFacts(detail)) => Self::with_diagnostic_facts(
387                self.class,
388                origin,
389                detail.diagnostic.detail().copied(),
390                detail.facts,
391            ),
392            _ => Self::classified(self.class, origin),
393        }
394    }
395
396    /// Construct an index-origin invariant violation.
397    #[cold]
398    #[inline(never)]
399    pub(crate) fn index_invariant() -> Self {
400        Self::new(ErrorClass::InvariantViolation, ErrorOrigin::Index)
401    }
402
403    /// Construct the canonical index field-count invariant for key building.
404    pub(crate) fn index_key_field_count_exceeds_max(
405        entity_tag: u64,
406        physical_generation: u64,
407        field_count: usize,
408        max_fields: usize,
409    ) -> Self {
410        Self::with_diagnostic_facts(
411            ErrorClass::InvariantViolation,
412            ErrorOrigin::Index,
413            None,
414            vec![
415                (diagnostic_code::DiagnosticFactTag::EntityTag, entity_tag),
416                (
417                    diagnostic_code::DiagnosticFactTag::PhysicalGeneration,
418                    physical_generation,
419                ),
420                (
421                    diagnostic_code::DiagnosticFactTag::ComponentKind,
422                    diagnostic_code::DiagnosticComponentKind::IndexKey.raw(),
423                ),
424                (
425                    diagnostic_code::DiagnosticFactTag::ActualArity,
426                    field_count as u64,
427                ),
428                (
429                    diagnostic_code::DiagnosticFactTag::Maximum,
430                    max_fields as u64,
431                ),
432            ],
433        )
434    }
435
436    /// Construct the canonical index-expression source-type mismatch invariant.
437    pub(crate) fn index_expression_source_type_mismatch(
438        _index_name: &str,
439        _expression: impl Sized,
440        _expected: impl Sized,
441        _source_label: &str,
442    ) -> Self {
443        Self::index_invariant()
444    }
445
446    /// Construct a planner-origin invariant violation for executor-boundary
447    /// contract drift.
448    #[cold]
449    #[inline(never)]
450    pub(crate) fn planner_executor_invariant() -> Self {
451        Self::new(ErrorClass::InvariantViolation, ErrorOrigin::Planner)
452    }
453
454    /// Construct a query-origin invariant violation for executor-boundary
455    /// contract drift.
456    #[cold]
457    #[inline(never)]
458    pub(crate) fn query_executor_invariant() -> Self {
459        Self::new(ErrorClass::InvariantViolation, ErrorOrigin::Query)
460    }
461
462    /// Construct a cursor-origin invariant violation for executor-boundary
463    /// contract drift.
464    #[cold]
465    #[inline(never)]
466    pub(crate) fn cursor_executor_invariant() -> Self {
467        Self::new(ErrorClass::InvariantViolation, ErrorOrigin::Cursor)
468    }
469
470    /// Construct an executor-origin invariant violation.
471    #[cold]
472    #[inline(never)]
473    pub(crate) fn executor_invariant() -> Self {
474        Self::new(ErrorClass::InvariantViolation, ErrorOrigin::Executor)
475    }
476
477    /// Construct an executor-origin internal error.
478    #[cold]
479    #[inline(never)]
480    pub(crate) fn executor_internal() -> Self {
481        Self::new(ErrorClass::Internal, ErrorOrigin::Executor)
482    }
483
484    /// Construct an executor-origin unsupported error.
485    #[cold]
486    #[inline(never)]
487    pub(crate) fn executor_unsupported() -> Self {
488        Self::new(ErrorClass::Unsupported, ErrorOrigin::Executor)
489    }
490
491    /// Construct an executor-origin database-owned-field authorship rejection.
492    #[cold]
493    #[inline(never)]
494    pub(crate) fn mutation_database_owned_field_explicit(
495        context: MutationDiagnosticContext,
496        field_id: u32,
497    ) -> Self {
498        Self::mutation_boundary_with_facts(
499            ErrorClass::Unsupported,
500            diagnostic_code::RuntimeBoundaryCode::MutationDatabaseOwnedFieldExplicit,
501            context.facts(Some(field_id)),
502        )
503    }
504
505    /// Construct an executor-origin required-field omission rejection.
506    #[must_use]
507    #[cold]
508    #[inline(never)]
509    pub(crate) fn mutation_required_field_missing(
510        context: MutationDiagnosticContext,
511        field_id: u32,
512    ) -> Self {
513        Self::mutation_boundary_with_facts(
514            ErrorClass::Unsupported,
515            diagnostic_code::RuntimeBoundaryCode::MutationRequiredFieldMissing,
516            context.facts(Some(field_id)),
517        )
518    }
519
520    /// Construct an executor-origin managed-timestamp clock regression.
521    #[must_use]
522    #[cold]
523    #[inline(never)]
524    pub(crate) fn mutation_managed_timestamp_regression(
525        context: MutationDiagnosticContext,
526    ) -> Self {
527        Self::mutation_boundary_with_facts(
528            ErrorClass::InvariantViolation,
529            diagnostic_code::RuntimeBoundaryCode::MutationManagedTimestampRegression,
530            context.facts(None),
531        )
532    }
533
534    /// Construct an executor-origin accepted constraint or activation-gate violation.
535    pub(crate) fn mutation_constraint_violation(context: AcceptedConstraintFactContext) -> Self {
536        Self::mutation_boundary_with_facts(
537            ErrorClass::InvariantViolation,
538            diagnostic_code::RuntimeBoundaryCode::ConstraintViolation,
539            context.facts(),
540        )
541    }
542
543    /// Construct an executor-origin corruption failure for row-constraint authority.
544    pub(crate) fn accepted_row_constraint_program_corrupt() -> Self {
545        Self {
546            class: ErrorClass::Corruption,
547            origin: ErrorOrigin::Executor,
548            detail: Some(ErrorDetail::Executor(
549                ExecutorErrorDetail::AcceptedRowConstraintProgramCorrupt,
550            )),
551        }
552    }
553
554    /// Construct one typed migration conflict for an incomplete activation gate.
555    pub(crate) fn mutation_constraint_activation_write_blocked(
556        context: AcceptedConstraintFactContext,
557    ) -> Self {
558        Self::mutation_boundary_with_facts(
559            ErrorClass::Conflict,
560            diagnostic_code::RuntimeBoundaryCode::ConstraintActivationWriteBlocked,
561            context.facts(),
562        )
563    }
564
565    /// Construct an executor-origin mutation unknown-field invariant.
566    pub(crate) fn mutation_structural_field_unknown(_entity_path: &str, _field_name: &str) -> Self {
567        Self::executor_invariant()
568    }
569
570    /// Construct a query-origin scalar page invariant for missing order at the cursor boundary.
571    pub(crate) fn scalar_page_cursor_boundary_order_required() -> Self {
572        Self::query_executor_invariant()
573    }
574
575    /// Construct a query-origin scalar page invariant for cursor-before-ordering drift.
576    pub(crate) fn scalar_page_cursor_boundary_after_ordering_required() -> Self {
577        Self::query_executor_invariant()
578    }
579
580    /// Construct a query-origin scalar page invariant for pagination-before-ordering drift.
581    pub(crate) fn scalar_page_pagination_after_ordering_required() -> Self {
582        Self::query_executor_invariant()
583    }
584
585    /// Construct a query-origin fast-stream invariant for route kind/request mismatch.
586    pub(crate) fn fast_stream_route_kind_request_match_required() -> Self {
587        Self::query_executor_invariant()
588    }
589
590    /// Construct a query-origin scan invariant for missing index-prefix executable specs.
591    pub(crate) fn secondary_index_prefix_spec_required() -> Self {
592        Self::query_executor_invariant()
593    }
594
595    /// Construct a query-origin scan invariant for missing index-range executable specs.
596    pub(crate) fn index_range_limit_spec_required() -> Self {
597        Self::query_executor_invariant()
598    }
599
600    /// Construct an executor-origin mutation conflict for duplicate atomic save keys.
601    #[cold]
602    #[inline(never)]
603    pub(crate) fn mutation_atomic_save_duplicate_key(
604        entity_tag: u64,
605        first_position: u32,
606        duplicate_position: u32,
607    ) -> Self {
608        Self::mutation_boundary_with_facts(
609            ErrorClass::Conflict,
610            diagnostic_code::RuntimeBoundaryCode::MutationBatchDuplicateKey,
611            vec![
612                (diagnostic_code::DiagnosticFactTag::EntityTag, entity_tag),
613                (
614                    diagnostic_code::DiagnosticFactTag::FirstBatchPosition,
615                    u64::from(first_position),
616                ),
617                (
618                    diagnostic_code::DiagnosticFactTag::DuplicateBatchPosition,
619                    u64::from(duplicate_position),
620                ),
621            ],
622        )
623    }
624
625    /// Construct an executor-origin empty mixed-mutation batch rejection.
626    #[cold]
627    #[inline(never)]
628    pub(crate) fn mutation_batch_empty() -> Self {
629        Self::mutation_boundary_with_facts(
630            ErrorClass::Unsupported,
631            diagnostic_code::RuntimeBoundaryCode::MutationBatchEmpty,
632            vec![(diagnostic_code::DiagnosticFactTag::ActualCount, 0)],
633        )
634    }
635
636    /// Construct an executor-origin mixed-mutation item-bound rejection.
637    #[cold]
638    #[inline(never)]
639    pub(crate) fn mutation_batch_too_many_items(actual_count: usize, limit: usize) -> Self {
640        Self::mutation_boundary_with_facts(
641            ErrorClass::Unsupported,
642            diagnostic_code::RuntimeBoundaryCode::MutationBatchTooManyItems,
643            vec![
644                (
645                    diagnostic_code::DiagnosticFactTag::ActualCount,
646                    actual_count as u64,
647                ),
648                (diagnostic_code::DiagnosticFactTag::Limit, limit as u64),
649            ],
650        )
651    }
652
653    /// Construct an executor-origin mixed-mutation staged-byte-bound rejection.
654    #[cold]
655    #[inline(never)]
656    pub(crate) fn mutation_batch_staged_bytes_exceeded(
657        actual_bytes: Option<usize>,
658        limit: usize,
659    ) -> Self {
660        let mut facts = Vec::with_capacity(1 + usize::from(actual_bytes.is_some()));
661        if let Some(actual_bytes) = actual_bytes {
662            facts.push((
663                diagnostic_code::DiagnosticFactTag::ActualLength,
664                actual_bytes as u64,
665            ));
666        }
667        facts.push((diagnostic_code::DiagnosticFactTag::Limit, limit as u64));
668        Self::mutation_boundary_with_facts(
669            ErrorClass::Unsupported,
670            diagnostic_code::RuntimeBoundaryCode::MutationBatchStagedBytesExceeded,
671            facts,
672        )
673    }
674
675    /// Construct an executor-origin mixed-mutation result-byte-bound rejection.
676    #[cold]
677    #[inline(never)]
678    pub(crate) fn mutation_batch_result_bytes_exceeded(actual_bytes: usize, limit: usize) -> Self {
679        Self::mutation_boundary_with_facts(
680            ErrorClass::Unsupported,
681            diagnostic_code::RuntimeBoundaryCode::MutationBatchResultBytesExceeded,
682            vec![
683                (
684                    diagnostic_code::DiagnosticFactTag::ActualLength,
685                    actual_bytes as u64,
686                ),
687                (diagnostic_code::DiagnosticFactTag::Limit, limit as u64),
688            ],
689        )
690    }
691
692    /// Construct an executor-origin mixed-entity batch rejection.
693    #[cold]
694    #[inline(never)]
695    pub(crate) fn mutation_batch_entity_mismatch(
696        batch_position: u32,
697        expected_entity_tag: u64,
698        actual_entity_tag: u64,
699    ) -> Self {
700        Self::mutation_boundary_with_facts(
701            ErrorClass::Conflict,
702            diagnostic_code::RuntimeBoundaryCode::MutationBatchEntityMismatch,
703            vec![
704                (
705                    diagnostic_code::DiagnosticFactTag::BatchPosition,
706                    u64::from(batch_position),
707                ),
708                (
709                    diagnostic_code::DiagnosticFactTag::ExpectedEntityTag,
710                    expected_entity_tag,
711                ),
712                (
713                    diagnostic_code::DiagnosticFactTag::ActualEntityTag,
714                    actual_entity_tag,
715                ),
716            ],
717        )
718    }
719
720    /// Construct an executor-origin mutation invariant for index-store generation drift.
721    pub(crate) fn mutation_index_store_generation_changed(
722        _expected_generation: u64,
723        _observed_generation: u64,
724    ) -> Self {
725        Self::executor_invariant()
726    }
727
728    /// Construct a planner-origin invariant violation.
729    #[cold]
730    #[inline(never)]
731    pub(crate) fn planner_invariant() -> Self {
732        Self::new(ErrorClass::InvariantViolation, ErrorOrigin::Planner)
733    }
734
735    /// Construct a planner-origin invalid-logical-plan invariant.
736    pub(crate) fn query_invalid_logical_plan() -> Self {
737        Self::planner_invariant()
738    }
739
740    /// Construct a store-origin invariant violation.
741    pub(crate) fn store_invariant() -> Self {
742        Self::new(ErrorClass::InvariantViolation, ErrorOrigin::Store)
743    }
744
745    /// Construct a store-origin internal error.
746    #[cold]
747    #[inline(never)]
748    pub(crate) fn store_internal() -> Self {
749        Self::new(ErrorClass::Internal, ErrorOrigin::Store)
750    }
751
752    /// Construct the canonical unconfigured commit-memory id internal error.
753    pub(crate) fn commit_memory_id_unconfigured() -> Self {
754        Self::store_internal()
755    }
756
757    /// Construct the canonical initialized commit-store lookup invariant.
758    pub(crate) fn commit_store_uninitialized() -> Self {
759        Self::store_invariant()
760    }
761
762    /// Construct the canonical commit-memory id mismatch internal error.
763    pub(crate) fn commit_memory_id_mismatch(cached_id: u8, configured_id: u8) -> Self {
764        Self::with_diagnostic_facts(
765            ErrorClass::Internal,
766            ErrorOrigin::Store,
767            None,
768            vec![
769                (
770                    diagnostic_code::DiagnosticFactTag::ExpectedMemoryId,
771                    u64::from(cached_id),
772                ),
773                (
774                    diagnostic_code::DiagnosticFactTag::ActualMemoryId,
775                    u64::from(configured_id),
776                ),
777            ],
778        )
779    }
780
781    /// Construct the canonical commit-memory stable-key mismatch internal error.
782    pub(crate) fn commit_memory_stable_key_mismatch(
783        _cached_key: &str,
784        _configured_key: &str,
785    ) -> Self {
786        Self::store_internal()
787    }
788
789    /// Construct the canonical database-incarnation generation failure.
790    pub(crate) fn database_incarnation_generation_failed() -> Self {
791        Self::store_internal()
792    }
793
794    /// Construct the canonical zero database-incarnation corruption error.
795    pub(crate) fn database_incarnation_invalid() -> Self {
796        Self::store_corruption()
797    }
798
799    /// Construct a recovery-origin incompatible store-format error.
800    pub(crate) fn recovery_unsupported_database_format(found: Option<u16>, required: u16) -> Self {
801        Self {
802            class: ErrorClass::IncompatiblePersistedFormat,
803            origin: ErrorOrigin::Recovery,
804            detail: Some(ErrorDetail::Recovery(
805                RecoveryErrorDetail::UnsupportedFormatVersion { found, required },
806            )),
807        }
808    }
809
810    /// Construct a recovery-origin malformed store-format marker error.
811    pub(crate) fn recovery_malformed_database_format_marker(
812        reason: RecoveryFormatMarkerError,
813    ) -> Self {
814        Self {
815            class: ErrorClass::Corruption,
816            origin: ErrorOrigin::Recovery,
817            detail: Some(ErrorDetail::Recovery(
818                RecoveryErrorDetail::MalformedFormatMarker { reason },
819            )),
820        }
821    }
822
823    /// Construct a recovery-origin boot control-memory failure.
824    pub(crate) fn recovery_database_format_control_unavailable() -> Self {
825        Self::new(ErrorClass::Internal, ErrorOrigin::Recovery)
826    }
827
828    /// Construct a commit control-memory growth failure.
829    pub(crate) fn commit_control_memory_growth_failed() -> Self {
830        Self::store_internal()
831    }
832
833    /// Construct a store-format memory registration failure.
834    #[cfg(not(test))]
835    pub(crate) fn database_format_memory_registration_failed(_err: impl Sized) -> Self {
836        Self::store_internal()
837    }
838
839    /// Construct the canonical recovered-effect verification failure.
840    pub(crate) fn recovery_effect_verification_failed() -> Self {
841        Self::store_corruption()
842    }
843
844    /// Construct an index-origin internal error.
845    #[cold]
846    #[inline(never)]
847    pub(crate) fn index_internal() -> Self {
848        Self::new(ErrorClass::Internal, ErrorOrigin::Index)
849    }
850
851    /// Construct the canonical missing old entity-key internal error for structural index removal.
852    pub(crate) fn structural_index_removal_entity_key_required() -> Self {
853        Self::index_internal()
854    }
855
856    /// Construct the canonical missing new entity-key internal error for structural index insertion.
857    pub(crate) fn structural_index_insertion_entity_key_required() -> Self {
858        Self::index_internal()
859    }
860
861    /// Construct the canonical missing old entity-key internal error for index commit-op removal.
862    pub(crate) fn index_commit_op_old_entity_key_required() -> Self {
863        Self::index_internal()
864    }
865
866    /// Construct the canonical missing new entity-key internal error for index commit-op insertion.
867    pub(crate) fn index_commit_op_new_entity_key_required() -> Self {
868        Self::index_internal()
869    }
870
871    /// Construct a query-origin internal error.
872    #[cfg(test)]
873    pub(crate) fn query_internal() -> Self {
874        Self::new(ErrorClass::Internal, ErrorOrigin::Query)
875    }
876
877    /// Construct a query-origin unsupported error.
878    #[cold]
879    #[inline(never)]
880    pub(crate) fn query_unsupported() -> Self {
881        Self::new(ErrorClass::Unsupported, ErrorOrigin::Query)
882    }
883
884    /// Construct a query-origin conflict for execution against a superseded
885    /// accepted schema revision.
886    #[cold]
887    #[inline(never)]
888    pub(crate) fn query_stale_accepted_schema_revision(
889        expected_revision: u64,
890        current_revision: Option<u64>,
891    ) -> Self {
892        let mut facts = Vec::with_capacity(1 + usize::from(current_revision.is_some()));
893        facts.push((
894            diagnostic_code::DiagnosticFactTag::ExpectedRevision,
895            expected_revision,
896        ));
897        if let Some(current_revision) = current_revision {
898            facts.push((
899                diagnostic_code::DiagnosticFactTag::CurrentRevision,
900                current_revision,
901            ));
902        }
903        Self::with_diagnostic_facts(ErrorClass::Conflict, ErrorOrigin::Query, None, facts)
904    }
905
906    /// Construct a query-origin SQL DDL admission error with structured detail.
907    #[cold]
908    #[inline(never)]
909    #[cfg(feature = "sql")]
910    pub(crate) fn query_schema_ddl_admission(error: SchemaDdlAdmissionError) -> Self {
911        Self {
912            class: ErrorClass::Unsupported,
913            origin: ErrorOrigin::Query,
914            detail: Some(ErrorDetail::Query(QueryErrorDetail::SchemaDdlAdmission {
915                error,
916            })),
917        }
918    }
919
920    /// Construct a query-origin numeric overflow error with structured detail.
921    #[cold]
922    #[inline(never)]
923    pub(crate) fn query_numeric_overflow() -> Self {
924        Self {
925            class: ErrorClass::Unsupported,
926            origin: ErrorOrigin::Query,
927            detail: Some(ErrorDetail::Query(QueryErrorDetail::NumericOverflow)),
928        }
929    }
930
931    /// Construct a query-origin non-representable numeric result error with
932    /// structured detail.
933    #[cold]
934    #[inline(never)]
935    pub(crate) fn query_numeric_not_representable() -> Self {
936        Self {
937            class: ErrorClass::Unsupported,
938            origin: ErrorOrigin::Query,
939            detail: Some(ErrorDetail::Query(
940                QueryErrorDetail::NumericNotRepresentable,
941            )),
942        }
943    }
944
945    /// Construct a serialize-origin internal error.
946    #[cold]
947    #[inline(never)]
948    pub(crate) fn serialize_internal() -> Self {
949        Self::new(ErrorClass::Internal, ErrorOrigin::Serialize)
950    }
951
952    /// Construct the canonical persisted-row encode internal error.
953    pub(crate) fn persisted_row_encode_failed(_detail: impl Sized) -> Self {
954        Self::persisted_row_encode_internal()
955    }
956
957    /// Construct the compact persisted-row encode internal error.
958    pub(crate) fn persisted_row_encode_internal() -> Self {
959        Self::serialize_internal()
960    }
961
962    /// Construct the compact persisted-row field encode internal error.
963    pub(crate) fn persisted_row_field_encode_internal(_field_name: &str) -> Self {
964        Self::persisted_row_encode_internal()
965    }
966
967    /// Construct a store-origin corruption error.
968    #[cold]
969    #[inline(never)]
970    pub(crate) fn store_corruption() -> Self {
971        Self::new(ErrorClass::Corruption, ErrorOrigin::Store)
972    }
973
974    /// Construct a store-origin commit-marker corruption error.
975    pub(crate) fn commit_corruption() -> Self {
976        Self::store_corruption()
977    }
978
979    /// Construct a store-origin commit-marker component corruption error.
980    pub(crate) fn commit_component_corruption() -> Self {
981        Self::commit_corruption()
982    }
983
984    /// Construct the canonical commit-marker id generation internal error.
985    pub(crate) fn commit_id_generation_failed() -> Self {
986        Self::store_internal()
987    }
988
989    /// Construct the canonical commit-marker payload u32-length-limit error.
990    pub(crate) fn commit_marker_payload_exceeds_u32_length_limit() -> Self {
991        Self::store_unsupported()
992    }
993
994    /// Construct the canonical commit-marker component invalid-length corruption error.
995    pub(crate) fn commit_component_length_invalid(actual_length: usize, limit: usize) -> Self {
996        Self::with_diagnostic_facts(
997            ErrorClass::Corruption,
998            ErrorOrigin::Store,
999            None,
1000            vec![
1001                (
1002                    diagnostic_code::DiagnosticFactTag::ComponentKind,
1003                    diagnostic_code::DiagnosticComponentKind::CommitDataKey.raw(),
1004                ),
1005                (
1006                    diagnostic_code::DiagnosticFactTag::ActualLength,
1007                    actual_length as u64,
1008                ),
1009                (diagnostic_code::DiagnosticFactTag::Limit, limit as u64),
1010            ],
1011        )
1012    }
1013
1014    /// Construct the canonical commit-marker max-size corruption error.
1015    pub(crate) fn commit_marker_exceeds_max_size() -> Self {
1016        Self::commit_corruption()
1017    }
1018
1019    /// Construct the canonical commit-control slot max-size unsupported error.
1020    pub(crate) fn commit_control_slot_exceeds_max_size() -> Self {
1021        Self::store_unsupported()
1022    }
1023
1024    /// Construct the canonical commit-control marker-bytes length-limit error.
1025    pub(crate) fn commit_control_slot_marker_bytes_exceed_u32_length_limit() -> Self {
1026        Self::store_unsupported()
1027    }
1028
1029    /// Construct the canonical startup index-rebuild invalid-data-key corruption error.
1030    pub(crate) fn startup_index_rebuild_invalid_data_key() -> Self {
1031        Self::store_corruption()
1032    }
1033
1034    /// Construct an index-origin corruption error.
1035    #[cold]
1036    #[inline(never)]
1037    pub(crate) fn index_corruption() -> Self {
1038        Self::new(ErrorClass::Corruption, ErrorOrigin::Index)
1039    }
1040
1041    /// Construct the canonical unique-validation corruption wrapper.
1042    pub(crate) fn index_unique_validation_corruption() -> Self {
1043        Self::index_plan_index_corruption()
1044    }
1045
1046    /// Construct the canonical structural index-entry corruption wrapper.
1047    pub(crate) fn structural_index_entry_corruption() -> Self {
1048        Self::index_plan_index_corruption()
1049    }
1050
1051    /// Construct the canonical missing new entity-key invariant during unique validation.
1052    pub(crate) fn index_unique_validation_entity_key_required() -> Self {
1053        Self::index_invariant()
1054    }
1055
1056    /// Construct the canonical unique-validation structural row-decode corruption error.
1057    pub(crate) fn index_unique_validation_row_deserialize_failed() -> Self {
1058        Self::index_plan_serialize_corruption()
1059    }
1060
1061    /// Construct the canonical unique-validation primary-key slot decode corruption error.
1062    pub(crate) fn index_unique_validation_primary_key_decode_failed() -> Self {
1063        Self::index_plan_serialize_corruption()
1064    }
1065
1066    /// Construct the canonical unique-validation stored key rebuild corruption error.
1067    pub(crate) fn index_unique_validation_key_rebuild_failed() -> Self {
1068        Self::index_plan_serialize_corruption()
1069    }
1070
1071    /// Construct the canonical unique-validation missing-row corruption error.
1072    pub(crate) fn index_unique_validation_row_required() -> Self {
1073        Self::index_plan_store_corruption()
1074    }
1075
1076    /// Construct the canonical index-only predicate missing-component invariant.
1077    pub(crate) fn index_only_predicate_component_required() -> Self {
1078        Self::index_invariant()
1079    }
1080
1081    /// Construct the canonical index-scan continuation-envelope invariant.
1082    pub(crate) fn index_scan_continuation_anchor_within_envelope_required() -> Self {
1083        Self::index_invariant()
1084    }
1085
1086    /// Construct the canonical index-scan continuation-advancement invariant.
1087    pub(crate) fn index_scan_continuation_advancement_required() -> Self {
1088        Self::index_invariant()
1089    }
1090
1091    /// Construct the canonical index-scan key-decode corruption error.
1092    pub(crate) fn index_scan_key_corrupted_during(
1093        _context: &'static str,
1094        _err: impl Sized,
1095    ) -> Self {
1096        Self::index_corruption()
1097    }
1098
1099    /// Construct the canonical index-scan missing projection-component invariant.
1100    pub(crate) fn index_projection_component_required(
1101        _index_name: &str,
1102        _component_index: usize,
1103    ) -> Self {
1104        Self::index_invariant()
1105    }
1106
1107    /// Construct the canonical scan-time index-entry decode corruption error.
1108    pub(crate) fn index_entry_decode_failed() -> Self {
1109        Self::index_corruption()
1110    }
1111
1112    /// Construct a serialize-origin corruption error.
1113    pub(crate) fn serialize_corruption() -> Self {
1114        Self::new(ErrorClass::Corruption, ErrorOrigin::Serialize)
1115    }
1116
1117    /// Construct the compact persisted-row decode corruption error.
1118    pub(crate) fn persisted_row_decode_corruption() -> Self {
1119        Self::serialize_corruption()
1120    }
1121
1122    /// Construct a persisted-row layout-window corruption error.
1123    pub(crate) fn persisted_row_layout_outside_accepted_window(
1124        row_layout: u32,
1125        history_floor: u32,
1126        current_layout: u32,
1127    ) -> Self {
1128        Self::with_diagnostic_facts(
1129            ErrorClass::Corruption,
1130            ErrorOrigin::Serialize,
1131            Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
1132                boundary:
1133                    diagnostic_code::RuntimeBoundaryCode::PersistedRowLayoutOutsideAcceptedWindow,
1134            }),
1135            vec![
1136                (
1137                    diagnostic_code::DiagnosticFactTag::RowLayout,
1138                    u64::from(row_layout),
1139                ),
1140                (
1141                    diagnostic_code::DiagnosticFactTag::HistoryFloor,
1142                    u64::from(history_floor),
1143                ),
1144                (
1145                    diagnostic_code::DiagnosticFactTag::CurrentLayout,
1146                    u64::from(current_layout),
1147                ),
1148            ],
1149        )
1150    }
1151
1152    /// Construct a persisted-row stamped-layout slot-count corruption error.
1153    pub(crate) fn persisted_row_slot_count_mismatch(
1154        row_layout: u32,
1155        expected_slot_count: usize,
1156        actual_slot_count: usize,
1157    ) -> Self {
1158        Self::with_diagnostic_facts(
1159            ErrorClass::Corruption,
1160            ErrorOrigin::Serialize,
1161            Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
1162                boundary: diagnostic_code::RuntimeBoundaryCode::PersistedRowSlotCountMismatch,
1163            }),
1164            vec![
1165                (
1166                    diagnostic_code::DiagnosticFactTag::RowLayout,
1167                    u64::from(row_layout),
1168                ),
1169                (
1170                    diagnostic_code::DiagnosticFactTag::ExpectedSlotCount,
1171                    expected_slot_count as u64,
1172                ),
1173                (
1174                    diagnostic_code::DiagnosticFactTag::ActualSlotCount,
1175                    actual_slot_count as u64,
1176                ),
1177            ],
1178        )
1179    }
1180
1181    /// Construct the canonical persisted-row field decode corruption error.
1182    pub(crate) fn persisted_row_field_decode_failed(field_name: &str, _detail: impl Sized) -> Self {
1183        Self::persisted_row_field_decode_corruption(field_name)
1184    }
1185
1186    /// Construct the compact persisted-row field decode corruption error.
1187    pub(crate) fn persisted_row_field_decode_corruption(_field_name: &str) -> Self {
1188        Self::persisted_row_decode_corruption()
1189    }
1190
1191    /// Construct the canonical persisted-row field-kind decode corruption error.
1192    pub(crate) fn persisted_row_field_kind_decode_failed(
1193        field_name: &str,
1194        _field_kind: impl fmt::Debug,
1195        _detail: impl Sized,
1196    ) -> Self {
1197        Self::persisted_row_field_decode_corruption(field_name)
1198    }
1199
1200    /// Construct the canonical persisted-row scalar-payload length corruption error.
1201    pub(crate) fn persisted_row_field_payload_exact_len_required(field_name: &str) -> Self {
1202        Self::persisted_row_field_decode_corruption(field_name)
1203    }
1204
1205    /// Construct the canonical persisted-row scalar-payload empty-body corruption error.
1206    pub(crate) fn persisted_row_field_payload_must_be_empty(field_name: &str) -> Self {
1207        Self::persisted_row_field_decode_corruption(field_name)
1208    }
1209
1210    /// Construct the canonical persisted-row scalar-payload invalid-byte corruption error.
1211    pub(crate) fn persisted_row_field_payload_invalid_byte(field_name: &str) -> Self {
1212        Self::persisted_row_field_decode_corruption(field_name)
1213    }
1214
1215    /// Construct the canonical persisted-row scalar-payload non-finite corruption error.
1216    pub(crate) fn persisted_row_field_payload_non_finite(field_name: &str) -> Self {
1217        Self::persisted_row_field_decode_corruption(field_name)
1218    }
1219
1220    /// Construct the canonical persisted-row invalid text payload corruption error.
1221    pub(crate) fn persisted_row_field_text_payload_invalid_utf8(field_name: &str) -> Self {
1222        Self::persisted_row_field_decode_corruption(field_name)
1223    }
1224
1225    /// Construct the canonical persisted-row structural slot-lookup invariant.
1226    pub(crate) fn persisted_row_slot_lookup_out_of_bounds(_model_path: &str, _slot: usize) -> Self {
1227        Self::index_invariant()
1228    }
1229
1230    /// Construct the canonical persisted-row structural slot-cache invariant.
1231    pub(crate) fn persisted_row_slot_cache_lookup_out_of_bounds(
1232        _model_path: &str,
1233        _slot: usize,
1234    ) -> Self {
1235        Self::index_invariant()
1236    }
1237
1238    /// Construct the canonical persisted-row primary-key decode corruption error.
1239    pub(crate) fn persisted_row_primary_key_not_primary_key_encodable(
1240        _data_key: impl fmt::Debug,
1241        _detail: impl Sized,
1242    ) -> Self {
1243        Self::persisted_row_decode_corruption()
1244    }
1245
1246    /// Construct the canonical persisted-row missing primary-key slot corruption error.
1247    pub(crate) fn persisted_row_primary_key_slot_missing(_data_key: impl fmt::Debug) -> Self {
1248        Self::persisted_row_decode_corruption()
1249    }
1250
1251    /// Construct the canonical persisted-row key mismatch corruption error.
1252    pub(crate) fn persisted_row_key_mismatch() -> Self {
1253        Self::store_corruption()
1254    }
1255
1256    /// Construct the canonical persisted-row missing declared-field corruption error.
1257    pub(crate) fn persisted_row_declared_field_missing(field_name: &str) -> Self {
1258        Self::persisted_row_field_decode_corruption(field_name)
1259    }
1260
1261    /// Construct the canonical reverse-index ordinal overflow internal error.
1262    pub(crate) fn reverse_index_ordinal_overflow(
1263        _source_path: &str,
1264        _field_name: &str,
1265        _target_path: &str,
1266        _detail: impl Sized,
1267    ) -> Self {
1268        Self::index_internal()
1269    }
1270
1271    /// Construct the canonical reverse-index entry corruption error.
1272    pub(crate) fn reverse_index_entry_corrupted(
1273        _source_path: &str,
1274        _field_name: &str,
1275        _target_path: &str,
1276        _index_key: impl fmt::Debug,
1277        _detail: impl Sized,
1278    ) -> Self {
1279        Self::index_corruption()
1280    }
1281
1282    /// Construct the canonical relation-target store missing internal error.
1283    pub(crate) fn relation_target_store_missing(
1284        _source_path: &str,
1285        _field_name: &str,
1286        _target_path: &str,
1287        _store_path: &str,
1288        _detail: impl Sized,
1289    ) -> Self {
1290        Self::executor_internal()
1291    }
1292
1293    /// Construct one accepted relation target primary-key arity mismatch.
1294    pub(crate) fn relation_target_primary_key_arity_mismatch(
1295        expected_arity: usize,
1296        actual_arity: usize,
1297    ) -> Self {
1298        Self::with_diagnostic_facts(
1299            ErrorClass::Internal,
1300            ErrorOrigin::Executor,
1301            None,
1302            vec![
1303                (
1304                    diagnostic_code::DiagnosticFactTag::ComponentKind,
1305                    diagnostic_code::DiagnosticComponentKind::RelationTargetPrimaryKey.raw(),
1306                ),
1307                (
1308                    diagnostic_code::DiagnosticFactTag::ExpectedArity,
1309                    expected_arity as u64,
1310                ),
1311                (
1312                    diagnostic_code::DiagnosticFactTag::ActualArity,
1313                    actual_arity as u64,
1314                ),
1315            ],
1316        )
1317    }
1318
1319    /// Construct the canonical relation-target key decode corruption error.
1320    pub(crate) fn relation_target_key_decode_failed(
1321        _context_label: &str,
1322        _source_path: &str,
1323        _field_name: &str,
1324        _target_path: &str,
1325        _detail: impl Sized,
1326    ) -> Self {
1327        Self::identity_corruption()
1328    }
1329
1330    /// Construct the canonical relation-target entity mismatch corruption error.
1331    pub(crate) fn relation_target_entity_mismatch(
1332        _context_label: &str,
1333        _source_path: &str,
1334        _field_name: &str,
1335        _target_path: &str,
1336        _target_entity_name: &str,
1337        expected_tag: u64,
1338        actual_tag: u64,
1339    ) -> Self {
1340        Self::with_diagnostic_facts(
1341            ErrorClass::Corruption,
1342            ErrorOrigin::Store,
1343            None,
1344            vec![
1345                (
1346                    diagnostic_code::DiagnosticFactTag::ExpectedEntityTag,
1347                    expected_tag,
1348                ),
1349                (
1350                    diagnostic_code::DiagnosticFactTag::ActualEntityTag,
1351                    actual_tag,
1352                ),
1353            ],
1354        )
1355    }
1356
1357    /// Construct the canonical relation-source row decode corruption error.
1358    pub(crate) fn relation_source_row_decode_failed(
1359        _source_path: &str,
1360        _field_name: &str,
1361        _target_path: &str,
1362        _detail: impl Sized,
1363    ) -> Self {
1364        Self::persisted_row_decode_corruption()
1365    }
1366
1367    /// Construct the canonical relation-source unsupported scalar relation-key corruption error.
1368    pub(crate) fn relation_source_row_unsupported_scalar_relation_key(
1369        _source_path: &str,
1370        _field_name: &str,
1371        _target_path: &str,
1372    ) -> Self {
1373        Self::persisted_row_decode_corruption()
1374    }
1375
1376    /// Construct the canonical unsupported relation key-kind corruption error.
1377    pub(crate) fn relation_source_row_unsupported_key_kind(_field_kind: impl fmt::Debug) -> Self {
1378        Self::persisted_row_decode_corruption()
1379    }
1380
1381    /// Construct the canonical covering-component empty-payload corruption error.
1382    pub(crate) fn bytes_covering_component_payload_empty() -> Self {
1383        Self::index_corruption()
1384    }
1385
1386    /// Construct the canonical covering-component truncated bool corruption error.
1387    pub(crate) fn bytes_covering_bool_payload_truncated() -> Self {
1388        Self::index_corruption()
1389    }
1390
1391    /// Construct the canonical covering-component invalid-length corruption error.
1392    pub(crate) fn bytes_covering_component_payload_invalid_length() -> Self {
1393        Self::index_corruption()
1394    }
1395
1396    /// Construct the canonical covering-component invalid-bool corruption error.
1397    pub(crate) fn bytes_covering_bool_payload_invalid_value() -> Self {
1398        Self::index_corruption()
1399    }
1400
1401    /// Construct the canonical covering-component invalid text terminator corruption error.
1402    pub(crate) fn bytes_covering_text_payload_invalid_terminator() -> Self {
1403        Self::index_corruption()
1404    }
1405
1406    /// Construct the canonical covering-component trailing-text corruption error.
1407    pub(crate) fn bytes_covering_text_payload_trailing_bytes() -> Self {
1408        Self::index_corruption()
1409    }
1410
1411    /// Construct the canonical covering-component invalid-UTF-8 text corruption error.
1412    pub(crate) fn bytes_covering_text_payload_invalid_utf8() -> Self {
1413        Self::index_corruption()
1414    }
1415
1416    /// Construct the canonical covering-component invalid text escape corruption error.
1417    pub(crate) fn bytes_covering_text_payload_invalid_escape_byte() -> Self {
1418        Self::index_corruption()
1419    }
1420
1421    /// Construct the canonical covering-component missing text terminator corruption error.
1422    pub(crate) fn bytes_covering_text_payload_missing_terminator() -> Self {
1423        Self::index_corruption()
1424    }
1425
1426    /// Construct an identity-origin corruption error.
1427    pub(crate) fn identity_corruption() -> Self {
1428        Self::new(ErrorClass::Corruption, ErrorOrigin::Identity)
1429    }
1430
1431    /// Construct the canonical identity-control-state corruption error.
1432    pub(crate) fn identity_state_corruption() -> Self {
1433        Self::identity_corruption()
1434    }
1435
1436    /// Construct the typed stale high-water conflict for identity publication.
1437    pub(crate) fn identity_state_conflict() -> Self {
1438        Self::new(ErrorClass::Conflict, ErrorOrigin::Identity)
1439    }
1440
1441    /// Construct the bounded identity-state inventory exhaustion error.
1442    pub(crate) fn identity_state_capacity_exhausted() -> Self {
1443        Self::new(ErrorClass::Unsupported, ErrorOrigin::Identity)
1444    }
1445
1446    /// Construct the exact unsigned identity-domain exhaustion error.
1447    pub(crate) fn identity_exhausted() -> Self {
1448        Self::new(ErrorClass::Unsupported, ErrorOrigin::Identity)
1449    }
1450
1451    /// Construct the bounded pre-key candidate-count exhaustion error.
1452    pub(crate) fn identity_candidate_count_exhausted() -> Self {
1453        Self::new(ErrorClass::Unsupported, ErrorOrigin::Identity)
1454    }
1455
1456    /// Construct a store-origin unsupported error.
1457    #[cold]
1458    #[inline(never)]
1459    pub(crate) fn store_unsupported() -> Self {
1460        Self::new(ErrorClass::Unsupported, ErrorOrigin::Store)
1461    }
1462
1463    /// Construct the typed optimistic/idempotency conflict for schema application.
1464    pub(crate) fn schema_application_conflict() -> Self {
1465        Self::new(ErrorClass::Conflict, ErrorOrigin::Store)
1466    }
1467
1468    /// Construct one typed source-migration lifecycle or planning result.
1469    pub(crate) fn schema_migration(reason: diagnostic_code::SchemaMigrationCode) -> Self {
1470        let class = match reason.diagnostic_code() {
1471            diagnostic_code::DiagnosticCode::RuntimeConflict => ErrorClass::Conflict,
1472            diagnostic_code::DiagnosticCode::RuntimeCorruption => ErrorClass::Corruption,
1473            diagnostic_code::DiagnosticCode::RuntimeUnsupported => ErrorClass::Unsupported,
1474            _ => ErrorClass::Internal,
1475        };
1476        Self {
1477            class,
1478            origin: ErrorOrigin::Store,
1479            detail: Some(ErrorDetail::Store(StoreError::SchemaMigration { reason })),
1480        }
1481    }
1482
1483    /// Construct the canonical schema DDL publication race error.
1484    pub(crate) fn schema_ddl_publication_race_lost(_entity_path: &str) -> Self {
1485        Self {
1486            class: ErrorClass::Unsupported,
1487            origin: ErrorOrigin::Store,
1488            detail: Some(ErrorDetail::Store(StoreError::SchemaDdlPublicationRaceLost)),
1489        }
1490    }
1491
1492    /// Construct the canonical current physical-rewrite migration rejection.
1493    #[cfg(feature = "sql")]
1494    pub(crate) fn schema_ddl_rewrite_requires_migration(_entity_path: &str) -> Self {
1495        Self {
1496            class: ErrorClass::Unsupported,
1497            origin: ErrorOrigin::Store,
1498            detail: Some(ErrorDetail::Store(
1499                StoreError::SchemaDdlRewriteRequiresMigration,
1500            )),
1501        }
1502    }
1503
1504    /// Construct the fail-closed journal mutation-revision exhaustion error.
1505    pub(crate) fn journal_mutation_revision_exhausted() -> Self {
1506        Self {
1507            class: ErrorClass::Unsupported,
1508            origin: ErrorOrigin::Store,
1509            detail: Some(ErrorDetail::Store(
1510                StoreError::JournalMutationRevisionExhausted,
1511            )),
1512        }
1513    }
1514
1515    /// Construct a bounded schema-transition resource rejection.
1516    pub(crate) fn schema_transition_budget_exceeded(
1517        resource: SchemaTransitionBudgetResource,
1518    ) -> Self {
1519        Self {
1520            class: ErrorClass::Unsupported,
1521            origin: ErrorOrigin::Store,
1522            detail: Some(ErrorDetail::Store(
1523                StoreError::SchemaTransitionBudgetExceeded { resource },
1524            )),
1525        }
1526    }
1527
1528    /// Construct the canonical unsupported persisted entity-tag store error.
1529    pub(crate) fn unsupported_entity_tag_in_data_store(
1530        _entity_tag: crate::types::EntityTag,
1531    ) -> Self {
1532        Self::store_unsupported()
1533    }
1534
1535    /// Construct the canonical commit-memory id registration failure.
1536    #[cfg(not(test))]
1537    pub(crate) fn commit_memory_id_registration_failed(_err: impl Sized) -> Self {
1538        Self::store_internal()
1539    }
1540
1541    /// Construct an index-origin unsupported error.
1542    pub(crate) fn index_unsupported() -> Self {
1543        Self::new(ErrorClass::Unsupported, ErrorOrigin::Index)
1544    }
1545
1546    /// Construct the canonical index-key component size-limit unsupported error.
1547    pub(crate) fn index_component_exceeds_max_size_at(
1548        entity_tag: u64,
1549        physical_generation: u64,
1550        component_index: usize,
1551        actual_length: usize,
1552        limit: usize,
1553    ) -> Self {
1554        Self::with_diagnostic_facts(
1555            ErrorClass::Unsupported,
1556            ErrorOrigin::Index,
1557            None,
1558            vec![
1559                (diagnostic_code::DiagnosticFactTag::EntityTag, entity_tag),
1560                (
1561                    diagnostic_code::DiagnosticFactTag::PhysicalGeneration,
1562                    physical_generation,
1563                ),
1564                (
1565                    diagnostic_code::DiagnosticFactTag::ComponentIndex,
1566                    component_index as u64,
1567                ),
1568                (
1569                    diagnostic_code::DiagnosticFactTag::ComponentKind,
1570                    diagnostic_code::DiagnosticComponentKind::IndexKeyComponent.raw(),
1571                ),
1572                (
1573                    diagnostic_code::DiagnosticFactTag::ActualLength,
1574                    actual_length as u64,
1575                ),
1576                (diagnostic_code::DiagnosticFactTag::Limit, limit as u64),
1577            ],
1578        )
1579    }
1580
1581    /// Construct the canonical index-key component size-limit error when the
1582    /// generic caller has not retained one accepted index identity.
1583    pub(crate) fn index_component_exceeds_max_size() -> Self {
1584        Self::index_unsupported()
1585    }
1586
1587    /// Construct a serialize-origin unsupported error.
1588    pub(crate) fn serialize_unsupported() -> Self {
1589        Self::new(ErrorClass::Unsupported, ErrorOrigin::Serialize)
1590    }
1591
1592    /// Construct a cursor-origin invalid-continuation error.
1593    pub(crate) fn cursor_invalid_continuation() -> Self {
1594        Self::new(ErrorClass::Unsupported, ErrorOrigin::Cursor)
1595    }
1596
1597    /// Construct a serialize-origin incompatible persisted-format error.
1598    pub(crate) fn serialize_incompatible_persisted_format() -> Self {
1599        Self::new(
1600            ErrorClass::IncompatiblePersistedFormat,
1601            ErrorOrigin::Serialize,
1602        )
1603    }
1604
1605    /// Construct a query-origin unsupported error preserving one SQL parser
1606    /// unsupported-feature code in structured error detail.
1607    #[cfg(feature = "sql")]
1608    pub(crate) fn query_unsupported_sql_feature(feature: diagnostic_code::SqlFeatureCode) -> Self {
1609        Self {
1610            class: ErrorClass::Unsupported,
1611            origin: ErrorOrigin::Query,
1612            detail: Some(ErrorDetail::Query(
1613                QueryErrorDetail::UnsupportedSqlFeature { feature },
1614            )),
1615        }
1616    }
1617
1618    /// Construct a query-origin unsupported SQL lowering error preserving one
1619    /// compact lowering reason in structured error detail.
1620    #[cfg(feature = "sql")]
1621    pub(crate) fn query_sql_lowering(reason: diagnostic_code::SqlLoweringCode) -> Self {
1622        Self {
1623            class: ErrorClass::Unsupported,
1624            origin: ErrorOrigin::Query,
1625            detail: Some(ErrorDetail::Query(QueryErrorDetail::SqlLowering { reason })),
1626        }
1627    }
1628
1629    /// Construct one query-origin SQL lowering error with bounded numeric context.
1630    #[cfg(feature = "sql")]
1631    pub(crate) fn query_sql_lowering_with_facts(
1632        reason: diagnostic_code::SqlLoweringCode,
1633        facts: Vec<(diagnostic_code::DiagnosticFactTag, u64)>,
1634    ) -> Self {
1635        Self::with_diagnostic_facts(
1636            ErrorClass::Unsupported,
1637            ErrorOrigin::Query,
1638            Some(diagnostic_code::DiagnosticDetail::SqlLowering { reason }),
1639            facts,
1640        )
1641    }
1642
1643    /// Construct a query-origin unsupported projection error preserving one
1644    /// compact projection reason in structured error detail.
1645    pub(crate) fn query_unsupported_projection(
1646        reason: diagnostic_code::QueryProjectionCode,
1647    ) -> Self {
1648        Self {
1649            class: ErrorClass::Unsupported,
1650            origin: ErrorOrigin::Query,
1651            detail: Some(ErrorDetail::Query(
1652                QueryErrorDetail::UnsupportedProjection { reason },
1653            )),
1654        }
1655    }
1656
1657    /// Construct a query-origin unsupported aggregate target-field error.
1658    pub(crate) fn query_unknown_aggregate_target_field() -> Self {
1659        Self {
1660            class: ErrorClass::Unsupported,
1661            origin: ErrorOrigin::Query,
1662            detail: Some(ErrorDetail::Query(
1663                QueryErrorDetail::UnknownAggregateTargetField,
1664            )),
1665        }
1666    }
1667
1668    /// Construct a query-origin unsupported error preserving one SQL endpoint
1669    /// surface mismatch in structured error detail.
1670    #[cfg(feature = "sql")]
1671    pub(crate) fn query_sql_surface_mismatch(
1672        mismatch: diagnostic_code::SqlSurfaceMismatchCode,
1673    ) -> Self {
1674        Self {
1675            class: ErrorClass::Unsupported,
1676            origin: ErrorOrigin::Query,
1677            detail: Some(ErrorDetail::Query(QueryErrorDetail::SqlSurfaceMismatch {
1678                mismatch,
1679            })),
1680        }
1681    }
1682
1683    /// Construct a query-origin unsupported SQL write boundary error.
1684    pub(crate) fn query_sql_write_boundary(
1685        boundary: diagnostic_code::SqlWriteBoundaryCode,
1686    ) -> Self {
1687        Self {
1688            class: ErrorClass::Unsupported,
1689            origin: ErrorOrigin::Query,
1690            detail: Some(ErrorDetail::Query(QueryErrorDetail::SqlWriteBoundary {
1691                boundary,
1692            })),
1693        }
1694    }
1695
1696    /// Construct one query-origin SQL write-boundary error with bounded numeric context.
1697    pub(crate) fn query_sql_write_boundary_with_facts(
1698        boundary: diagnostic_code::SqlWriteBoundaryCode,
1699        facts: Vec<(diagnostic_code::DiagnosticFactTag, u64)>,
1700    ) -> Self {
1701        Self::with_diagnostic_facts(
1702            ErrorClass::Unsupported,
1703            ErrorOrigin::Query,
1704            Some(diagnostic_code::DiagnosticDetail::SqlWriteBoundary { boundary }),
1705            facts,
1706        )
1707    }
1708
1709    pub fn store_not_found(_key: impl Sized) -> Self {
1710        Self {
1711            class: ErrorClass::NotFound,
1712            origin: ErrorOrigin::Store,
1713            detail: Some(ErrorDetail::Store(StoreError::NotFound)),
1714        }
1715    }
1716
1717    /// Construct a standardized unsupported-entity-path error.
1718    pub fn unsupported_entity_path(_path: impl Sized) -> Self {
1719        Self::store_unsupported()
1720    }
1721
1722    /// Construct an index-plan corruption error with a canonical prefix.
1723    #[cold]
1724    #[inline(never)]
1725    pub(crate) fn index_plan_corruption(origin: ErrorOrigin) -> Self {
1726        Self::new(ErrorClass::Corruption, origin)
1727    }
1728
1729    /// Construct an index-plan corruption error for index-origin failures.
1730    #[cold]
1731    #[inline(never)]
1732    pub(crate) fn index_plan_index_corruption() -> Self {
1733        Self::index_plan_corruption(ErrorOrigin::Index)
1734    }
1735
1736    /// Construct an index-plan corruption error for store-origin failures.
1737    #[cold]
1738    #[inline(never)]
1739    pub(crate) fn index_plan_store_corruption() -> Self {
1740        Self::index_plan_corruption(ErrorOrigin::Store)
1741    }
1742
1743    /// Construct an index-plan corruption error for serialize-origin failures.
1744    #[cold]
1745    #[inline(never)]
1746    pub(crate) fn index_plan_serialize_corruption() -> Self {
1747        Self::index_plan_corruption(ErrorOrigin::Serialize)
1748    }
1749
1750    /// Construct an index-plan invariant violation error with a canonical prefix.
1751    #[cfg(test)]
1752    pub(crate) fn index_plan_invariant(origin: ErrorOrigin) -> Self {
1753        Self::new(ErrorClass::InvariantViolation, origin)
1754    }
1755
1756    /// Construct an index-plan invariant violation error for store-origin failures.
1757    #[cfg(test)]
1758    pub(crate) fn index_plan_store_invariant() -> Self {
1759        Self::index_plan_invariant(ErrorOrigin::Store)
1760    }
1761
1762    /// Construct an index-origin conflict without claiming accepted identity.
1763    ///
1764    /// Live accepted uniqueness violations use compact accepted-constraint facts.
1765    /// Schema-domain staging and activation findings use this compact
1766    /// classification before an accepted write-admission diagnostic exists.
1767    pub(crate) fn index_conflict() -> Self {
1768        Self::new(ErrorClass::Conflict, ErrorOrigin::Index)
1769    }
1770}
1771
1772impl From<diagnostic_code::QueryReadAdmissionCode> for InternalError {
1773    fn from(reason: diagnostic_code::QueryReadAdmissionCode) -> Self {
1774        Self {
1775            class: ErrorClass::Unsupported,
1776            origin: ErrorOrigin::Query,
1777            detail: Some(ErrorDetail::Query(QueryErrorDetail::QueryReadAdmission {
1778                reason,
1779            })),
1780        }
1781    }
1782}
1783
1784impl fmt::Debug for InternalError {
1785    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1786        fmt_compact_diagnostic(
1787            f,
1788            self.diagnostic_code(),
1789            self.detail
1790                .as_ref()
1791                .and_then(ErrorDetail::diagnostic_detail),
1792        )
1793    }
1794}
1795
1796impl fmt::Display for InternalError {
1797    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1798        f.write_str(self.message())
1799    }
1800}
1801
1802impl std::error::Error for InternalError {}
1803
1804///
1805/// ConstraintValuePathComponent
1806///
1807/// Stable accepted identity or finite-value coordinate in one targeted-rule
1808/// violation. Display names are deliberately absent so renames cannot change
1809/// the diagnostic identity.
1810///
1811
1812#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
1813pub enum ConstraintValuePathComponent {
1814    /// Persisted root field whose admitted value was traversed.
1815    RootField { field_id: u32 },
1816
1817    /// Accepted record member selected by immutable composite/member identity.
1818    RecordMember {
1819        composite_type_id: u32,
1820        member_id: u32,
1821    },
1822
1823    /// Tuple element selected by accepted composite identity and ordinal.
1824    TupleElement {
1825        composite_type_id: u32,
1826        ordinal: u32,
1827    },
1828
1829    /// Transparent accepted newtype boundary.
1830    Newtype { composite_type_id: u32 },
1831
1832    /// Selected accepted enum variant.
1833    EnumVariant { enum_type_id: u32, variant_id: u32 },
1834
1835    /// List element in admitted order.
1836    ListElement { index: u32 },
1837
1838    /// Set element in canonical admitted order.
1839    SetElement { index: u32 },
1840
1841    /// Map key in canonical entry order.
1842    MapEntryKey { index: u32 },
1843
1844    /// Map value in canonical entry order.
1845    MapEntryValue { index: u32 },
1846}
1847
1848impl fmt::Display for ConstraintValuePathComponent {
1849    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1850        match self {
1851            Self::RootField { field_id } => write!(f, "field#{field_id}"),
1852            Self::RecordMember {
1853                composite_type_id,
1854                member_id,
1855            } => write!(f, "record#{composite_type_id}.member#{member_id}"),
1856            Self::TupleElement {
1857                composite_type_id,
1858                ordinal,
1859            } => write!(f, "tuple#{composite_type_id}[{ordinal}]"),
1860            Self::Newtype { composite_type_id } => write!(f, "newtype#{composite_type_id}"),
1861            Self::EnumVariant {
1862                enum_type_id,
1863                variant_id,
1864            } => write!(f, "enum#{enum_type_id}.variant#{variant_id}"),
1865            Self::ListElement { index } => write!(f, "list[{index}]"),
1866            Self::SetElement { index } => write!(f, "set[{index}]"),
1867            Self::MapEntryKey { index } => write!(f, "map[{index}].key"),
1868            Self::MapEntryValue { index } => write!(f, "map[{index}].value"),
1869        }
1870    }
1871}
1872
1873///
1874/// ConstraintValuePath
1875///
1876/// Bounded typed path to the first deterministic failing value occurrence.
1877///
1878
1879#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
1880pub struct ConstraintValuePath {
1881    components: Vec<ConstraintValuePathComponent>,
1882}
1883
1884impl ConstraintValuePath {
1885    /// Build one already-bounded accepted occurrence path.
1886    #[must_use]
1887    pub(crate) const fn new(components: Vec<ConstraintValuePathComponent>) -> Self {
1888        Self { components }
1889    }
1890
1891    /// Borrow the stable accepted components.
1892    #[must_use]
1893    pub const fn components(&self) -> &[ConstraintValuePathComponent] {
1894        self.components.as_slice()
1895    }
1896}
1897
1898impl fmt::Display for ConstraintValuePath {
1899    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1900        for (ordinal, component) in self.components.iter().enumerate() {
1901            if ordinal != 0 {
1902                f.write_str("/")?;
1903            }
1904            component.fmt(f)?;
1905        }
1906        Ok(())
1907    }
1908}
1909
1910///
1911/// ConstraintValidationFindingOutput
1912///
1913/// Bounded historical validation evidence returned only by explicit schema
1914/// validation operations. Names are resolved by host tooling from the exact
1915/// accepted fingerprint and immutable numeric identities.
1916///
1917
1918#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
1919pub struct ConstraintValidationFindingOutput {
1920    accepted_schema_fingerprint: [u8; 16],
1921    entity_tag: u64,
1922    constraint_id: u32,
1923    primary_key: Vec<u8>,
1924    field_ids: Vec<u32>,
1925    value_path: Option<ConstraintValuePath>,
1926    error_code: u16,
1927}
1928
1929impl ConstraintValidationFindingOutput {
1930    /// Build one already-bounded historical validation finding.
1931    #[must_use]
1932    pub(crate) const fn new(
1933        accepted_schema_fingerprint: [u8; 16],
1934        entity_tag: u64,
1935        constraint_id: u32,
1936        primary_key: Vec<u8>,
1937        field_ids: Vec<u32>,
1938        value_path: Option<ConstraintValuePath>,
1939        error_code: u16,
1940    ) -> Self {
1941        Self {
1942            accepted_schema_fingerprint,
1943            entity_tag,
1944            constraint_id,
1945            primary_key,
1946            field_ids,
1947            value_path,
1948            error_code,
1949        }
1950    }
1951
1952    /// Return the exact accepted-schema fingerprint that binds every numeric identity.
1953    #[must_use]
1954    pub const fn accepted_schema_fingerprint(&self) -> [u8; 16] {
1955        self.accepted_schema_fingerprint
1956    }
1957
1958    /// Return the stable accepted entity identity.
1959    #[must_use]
1960    pub const fn entity_tag(&self) -> u64 {
1961        self.entity_tag
1962    }
1963
1964    /// Return the stable accepted constraint identity.
1965    #[must_use]
1966    pub const fn constraint_id(&self) -> u32 {
1967        self.constraint_id
1968    }
1969
1970    /// Borrow the bounded canonical persisted primary-key locator.
1971    #[must_use]
1972    pub const fn primary_key(&self) -> &[u8] {
1973        self.primary_key.as_slice()
1974    }
1975
1976    /// Borrow immutable accepted field identities implicated by the finding.
1977    #[must_use]
1978    pub const fn field_ids(&self) -> &[u32] {
1979        self.field_ids.as_slice()
1980    }
1981
1982    /// Borrow the typed concrete value path for a targeted-rule violation.
1983    #[must_use]
1984    pub const fn value_path(&self) -> Option<&ConstraintValuePath> {
1985        self.value_path.as_ref()
1986    }
1987
1988    /// Return the compact stable error code for this exact failure.
1989    #[must_use]
1990    pub const fn error_code(&self) -> diagnostic_code::ErrorCode {
1991        diagnostic_code::ErrorCode::from_raw(self.error_code)
1992    }
1993
1994    /// Return the broad public error class derived from the compact code.
1995    #[must_use]
1996    pub const fn error_class(&self) -> diagnostic_code::ErrorClass {
1997        self.error_code().class()
1998    }
1999}
2000
2001/// Complete bounded numeric authority needed to publish E223 or E225 facts.
2002#[derive(Clone)]
2003pub(crate) struct AcceptedConstraintFactContext {
2004    fingerprint_method: u8,
2005    accepted_schema_fingerprint: [u8; 16],
2006    entity_tag: u64,
2007    constraint_id: u32,
2008    constraint_kind: diagnostic_code::DiagnosticConstraintKind,
2009    mutation: Option<MutationDiagnosticContext>,
2010    value_path: Option<ConstraintValuePath>,
2011}
2012
2013impl AcceptedConstraintFactContext {
2014    #[must_use]
2015    pub(crate) fn write_admission(
2016        fingerprint_method: u8,
2017        accepted_schema_fingerprint: [u8; 16],
2018        entity_tag: u64,
2019        constraint_id: u32,
2020        constraint_kind: diagnostic_code::DiagnosticConstraintKind,
2021        mutation: Option<MutationDiagnosticContext>,
2022        value_path: Option<ConstraintValuePath>,
2023    ) -> Self {
2024        debug_assert!(mutation.is_none_or(|context| context.entity_tag() == entity_tag));
2025        Self {
2026            fingerprint_method,
2027            accepted_schema_fingerprint,
2028            entity_tag,
2029            constraint_id,
2030            constraint_kind,
2031            mutation,
2032            value_path,
2033        }
2034    }
2035
2036    fn facts(self) -> Vec<(diagnostic_code::DiagnosticFactTag, u64)> {
2037        let high = u64::from_be_bytes([
2038            self.accepted_schema_fingerprint[0],
2039            self.accepted_schema_fingerprint[1],
2040            self.accepted_schema_fingerprint[2],
2041            self.accepted_schema_fingerprint[3],
2042            self.accepted_schema_fingerprint[4],
2043            self.accepted_schema_fingerprint[5],
2044            self.accepted_schema_fingerprint[6],
2045            self.accepted_schema_fingerprint[7],
2046        ]);
2047        let low = u64::from_be_bytes([
2048            self.accepted_schema_fingerprint[8],
2049            self.accepted_schema_fingerprint[9],
2050            self.accepted_schema_fingerprint[10],
2051            self.accepted_schema_fingerprint[11],
2052            self.accepted_schema_fingerprint[12],
2053            self.accepted_schema_fingerprint[13],
2054            self.accepted_schema_fingerprint[14],
2055            self.accepted_schema_fingerprint[15],
2056        ]);
2057        let path_len = self
2058            .value_path
2059            .as_ref()
2060            .map_or(0, |path| path.components().len());
2061        let mutation_fact_count = self.mutation.map_or(0, |mutation| {
2062            1 + usize::from(mutation.batch_position.is_some())
2063        });
2064        let mut facts = Vec::with_capacity(7 + mutation_fact_count + path_len);
2065        facts.push((
2066            diagnostic_code::DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
2067            u64::from(self.fingerprint_method),
2068        ));
2069        facts.push((
2070            diagnostic_code::DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
2071            high,
2072        ));
2073        facts.push((
2074            diagnostic_code::DiagnosticFactTag::AcceptedSchemaFingerprintLow,
2075            low,
2076        ));
2077        facts.push((
2078            diagnostic_code::DiagnosticFactTag::EntityTag,
2079            self.entity_tag,
2080        ));
2081        facts.push((
2082            diagnostic_code::DiagnosticFactTag::ConstraintId,
2083            u64::from(self.constraint_id),
2084        ));
2085        facts.push((
2086            diagnostic_code::DiagnosticFactTag::ConstraintKind,
2087            self.constraint_kind.raw(),
2088        ));
2089        facts.push((
2090            diagnostic_code::DiagnosticFactTag::ConstraintContext,
2091            diagnostic_code::DiagnosticConstraintContext::WriteAdmission.raw(),
2092        ));
2093        if let Some(mutation) = self.mutation {
2094            mutation.append_operation_facts(&mut facts);
2095        }
2096        if let Some(path) = self.value_path {
2097            for component in path.components {
2098                facts.push(constraint_value_path_fact(component));
2099            }
2100        }
2101        debug_assert!(facts.len() <= diagnostic_code::MAX_PUBLIC_DIAGNOSTIC_FACTS);
2102        facts
2103    }
2104}
2105
2106fn constraint_value_path_fact(
2107    component: ConstraintValuePathComponent,
2108) -> (diagnostic_code::DiagnosticFactTag, u64) {
2109    use diagnostic_code::DiagnosticFactTag;
2110    match component {
2111        ConstraintValuePathComponent::RootField { field_id } => {
2112            (DiagnosticFactTag::RootField, u64::from(field_id))
2113        }
2114        ConstraintValuePathComponent::RecordMember {
2115            composite_type_id,
2116            member_id,
2117        } => (
2118            DiagnosticFactTag::RecordMember,
2119            diagnostic_code::pack_u32_pair(composite_type_id, member_id),
2120        ),
2121        ConstraintValuePathComponent::TupleElement {
2122            composite_type_id,
2123            ordinal,
2124        } => (
2125            DiagnosticFactTag::TupleElement,
2126            diagnostic_code::pack_u32_pair(composite_type_id, ordinal),
2127        ),
2128        ConstraintValuePathComponent::Newtype { composite_type_id } => {
2129            (DiagnosticFactTag::Newtype, u64::from(composite_type_id))
2130        }
2131        ConstraintValuePathComponent::EnumVariant {
2132            enum_type_id,
2133            variant_id,
2134        } => (
2135            DiagnosticFactTag::EnumVariant,
2136            diagnostic_code::pack_u32_pair(enum_type_id, variant_id),
2137        ),
2138        ConstraintValuePathComponent::ListElement { index } => {
2139            (DiagnosticFactTag::ListElement, u64::from(index))
2140        }
2141        ConstraintValuePathComponent::SetElement { index } => {
2142            (DiagnosticFactTag::SetElement, u64::from(index))
2143        }
2144        ConstraintValuePathComponent::MapEntryKey { index } => {
2145            (DiagnosticFactTag::MapEntryKey, u64::from(index))
2146        }
2147        ConstraintValuePathComponent::MapEntryValue { index } => {
2148            (DiagnosticFactTag::MapEntryValue, u64::from(index))
2149        }
2150    }
2151}
2152
2153///
2154/// ErrorDetail
2155///
2156/// Structured, origin-specific error detail carried by [`InternalError`].
2157/// This enum is intentionally extensible.
2158///
2159
2160pub enum ErrorDetail {
2161    /// Compact code/detail plus safe numeric context for one public failure.
2162    DiagnosticFacts(Box<DiagnosticFactDetail>),
2163    /// Executor-owned mutation and query execution details.
2164    Executor(ExecutorErrorDetail),
2165    Store(StoreError),
2166    Query(QueryErrorDetail),
2167    Recovery(RecoveryErrorDetail),
2168    // Future-proofing:
2169    // Index(IndexError),
2170}
2171
2172/// Executor-specific structured error detail.
2173pub enum ExecutorErrorDetail {
2174    /// A complete insert or replacement omitted one or more required fields.
2175    MutationRequiredFieldMissing,
2176    /// A logical mutation would move accepted managed time backward.
2177    MutationManagedTimestampRegression,
2178    /// A caller explicitly authored a field owned by accepted database policy.
2179    MutationDatabaseOwnedFieldExplicit,
2180    /// A mixed structural mutation batch contained no operations.
2181    MutationBatchEmpty,
2182    /// A mixed structural mutation batch exceeded its operation-count bound.
2183    MutationBatchTooManyItems,
2184    /// A mixed structural mutation batch exceeded its staged-byte bound.
2185    MutationBatchStagedBytesExceeded,
2186    /// A mixed structural mutation result exceeded its encoded response bound.
2187    MutationBatchResultBytesExceeded,
2188    /// A mixed structural mutation batch resolved to more than one accepted entity.
2189    MutationBatchEntityMismatch,
2190    /// More than one mixed structural operation targeted the same accepted key.
2191    MutationBatchDuplicateKey,
2192    /// Accepted row-constraint metadata or compiled state was inconsistent.
2193    AcceptedRowConstraintProgramCorrupt,
2194}
2195
2196///
2197/// RecoveryErrorDetail
2198///
2199/// Recovery-origin structured error detail payload.
2200///
2201
2202pub enum RecoveryErrorDetail {
2203    UnsupportedFormatVersion { found: Option<u16>, required: u16 },
2204
2205    MalformedFormatMarker { reason: RecoveryFormatMarkerError },
2206}
2207
2208/// Store boot-marker corruption classification.
2209#[derive(Clone, Copy, Eq, PartialEq)]
2210pub enum RecoveryFormatMarkerError {
2211    Magic,
2212    Checksum,
2213    State,
2214}
2215
2216impl RecoveryFormatMarkerError {
2217    const fn diagnostic_decode_reason(self) -> diagnostic_code::DiagnosticDecodeReason {
2218        match self {
2219            Self::Magic => diagnostic_code::DiagnosticDecodeReason::RecoveryMarkerMagic,
2220            Self::Checksum => diagnostic_code::DiagnosticDecodeReason::RecoveryMarkerChecksum,
2221            Self::State => diagnostic_code::DiagnosticDecodeReason::RecoveryMarkerState,
2222        }
2223    }
2224}
2225
2226///
2227/// StoreError
2228///
2229/// Store-specific structured error detail.
2230/// Never returned directly; always wrapped in [`ErrorDetail::Store`].
2231///
2232
2233pub enum StoreError {
2234    NotFound,
2235
2236    Corrupt,
2237
2238    InvariantViolation,
2239
2240    SchemaDdlPublicationRaceLost,
2241
2242    SchemaDdlRewriteRequiresMigration,
2243
2244    SchemaMigration {
2245        reason: diagnostic_code::SchemaMigrationCode,
2246    },
2247
2248    SchemaRowLayoutVersionExhausted,
2249
2250    JournalMutationRevisionExhausted,
2251
2252    SchemaTransitionBudgetExceeded {
2253        resource: SchemaTransitionBudgetResource,
2254    },
2255
2256    /// A generated field would collide with an accepted DDL-owned slot.
2257    SchemaGeneratedFieldAfterDdlField,
2258
2259    /// A live generated constraint activation no longer matches its proposal.
2260    SchemaGeneratedConstraintActivationStale,
2261}
2262
2263///
2264/// QueryErrorDetail
2265///
2266/// Query-origin structured error detail payload.
2267///
2268
2269pub enum QueryErrorDetail {
2270    NumericOverflow,
2271
2272    NumericNotRepresentable,
2273
2274    UnsupportedSqlFeature {
2275        feature: diagnostic_code::SqlFeatureCode,
2276    },
2277
2278    SqlLowering {
2279        reason: diagnostic_code::SqlLoweringCode,
2280    },
2281
2282    UnsupportedProjection {
2283        reason: diagnostic_code::QueryProjectionCode,
2284    },
2285
2286    UnknownAggregateTargetField,
2287
2288    ResultShapeMismatch {
2289        reason: diagnostic_code::QueryResultShapeCode,
2290    },
2291
2292    QueryReadAdmission {
2293        reason: diagnostic_code::QueryReadAdmissionCode,
2294    },
2295
2296    SqlSurfaceMismatch {
2297        mismatch: diagnostic_code::SqlSurfaceMismatchCode,
2298    },
2299
2300    SqlWriteBoundary {
2301        boundary: diagnostic_code::SqlWriteBoundaryCode,
2302    },
2303
2304    SchemaDdlAdmission {
2305        error: SchemaDdlAdmissionError,
2306    },
2307
2308    StaleSchemaRevision,
2309}
2310
2311impl fmt::Display for QueryErrorDetail {
2312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2313        f.write_str(COMPACT_QUERY_DIAGNOSTIC_MESSAGE)
2314    }
2315}
2316
2317impl std::error::Error for QueryErrorDetail {}
2318
2319///
2320/// SchemaTransitionBudgetResource
2321///
2322/// Query-visible identity of the exact schema-transition resource cap that
2323/// rejected a complete validation or derived-state stage.
2324///
2325
2326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2327pub enum SchemaTransitionBudgetResource {
2328    /// Number of physical deletion keys retained for replacement.
2329    DeletionKeys,
2330    /// Number of row-derived projection entries retained for validation.
2331    ProjectionEntries,
2332    /// Deterministic projection and physical-classification work units.
2333    ProjectionWorkUnits,
2334    /// Number of authoritative source rows.
2335    SourceRows,
2336    /// Cumulative bytes of authoritative source rows.
2337    SourceRowBytes,
2338    /// Retained raw payloads plus deterministic-sort workspace bytes.
2339    StagedRawBytes,
2340}
2341
2342///
2343/// SchemaDdlAdmissionError
2344///
2345/// Stable query-visible SQL DDL admission reason. Human diagnostics may carry
2346/// extra version, fingerprint, and target facts beside this machine-readable
2347/// variant.
2348///
2349
2350#[derive(Clone, Copy, Eq, PartialEq)]
2351pub enum SchemaDdlAdmissionError {
2352    MissingExpectedSchemaVersion,
2353
2354    MissingNextSchemaVersion,
2355
2356    StaleExpectedSchemaVersion,
2357
2358    InvalidExpectedSchemaVersion,
2359
2360    InvalidNextSchemaVersion,
2361
2362    AcceptedSchemaChangeWithoutVersionBump,
2363
2364    EmptyVersionBump,
2365
2366    VersionGap,
2367
2368    VersionRollback,
2369
2370    FingerprintMethodMismatch,
2371
2372    UnsupportedTransitionClass,
2373
2374    PhysicalRunnerMissing,
2375
2376    ValidationFailed,
2377
2378    PublicationRaceLost,
2379
2380    InvalidAddColumnDefault,
2381
2382    InvalidAlterColumnDefault,
2383
2384    RowLayoutVersionExhausted,
2385
2386    GeneratedIndexDropRejected,
2387
2388    SchemaRewriteRequiresMigration,
2389
2390    SchemaTransitionBudgetExceeded {
2391        resource: SchemaTransitionBudgetResource,
2392    },
2393
2394    GeneratedFieldDefaultChangeRejected,
2395
2396    GeneratedFieldNullabilityChangeRejected,
2397}
2398
2399impl fmt::Display for SchemaDdlAdmissionError {
2400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2401        f.write_str(COMPACT_QUERY_DIAGNOSTIC_MESSAGE)
2402    }
2403}
2404
2405impl std::error::Error for SchemaDdlAdmissionError {}
2406
2407impl fmt::Debug for ErrorDetail {
2408    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2409        fmt_compact_diagnostic(f, self.diagnostic_code(), self.diagnostic_detail())
2410    }
2411}
2412
2413impl fmt::Debug for ExecutorErrorDetail {
2414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2415        fmt_compact_diagnostic(f, self.diagnostic_code(), self.diagnostic_detail())
2416    }
2417}
2418
2419impl fmt::Debug for StoreError {
2420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2421        fmt_compact_diagnostic(f, self.diagnostic_code(), self.diagnostic_detail())
2422    }
2423}
2424
2425impl fmt::Debug for QueryErrorDetail {
2426    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2427        fmt_compact_diagnostic(f, self.diagnostic_code(), self.diagnostic_detail())
2428    }
2429}
2430
2431impl fmt::Debug for RecoveryErrorDetail {
2432    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2433        fmt_compact_diagnostic(f, self.diagnostic_code(), self.diagnostic_detail())
2434    }
2435}
2436
2437impl fmt::Debug for RecoveryFormatMarkerError {
2438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2439        fmt_compact_diagnostic(
2440            f,
2441            diagnostic_code::DiagnosticCode::RuntimeCorruption,
2442            Some(diagnostic_code::DiagnosticDetail::RuntimeKind {
2443                kind: diagnostic_code::RuntimeErrorKind::Corruption,
2444            }),
2445        )
2446    }
2447}
2448
2449impl fmt::Debug for SchemaDdlAdmissionError {
2450    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2451        fmt_compact_diagnostic(
2452            f,
2453            diagnostic_code::DiagnosticCode::SchemaDdlAdmission,
2454            Some(diagnostic_code::DiagnosticDetail::SchemaDdlAdmission {
2455                reason: self.diagnostic_code(),
2456            }),
2457        )
2458    }
2459}
2460
2461fn fmt_compact_diagnostic(
2462    f: &mut fmt::Formatter<'_>,
2463    code: diagnostic_code::DiagnosticCode,
2464    detail: Option<diagnostic_code::DiagnosticDetail>,
2465) -> fmt::Result {
2466    write!(
2467        f,
2468        "{}",
2469        diagnostic_code::ErrorCode::from_parts(code, detail).raw()
2470    )
2471}
2472
2473impl ErrorDetail {
2474    /// Return the compact diagnostic code for this structured detail.
2475    #[must_use]
2476    pub const fn diagnostic_code(&self) -> diagnostic_code::DiagnosticCode {
2477        match self {
2478            Self::DiagnosticFacts(detail) => detail.diagnostic.code(),
2479            Self::Executor(error) => error.diagnostic_code(),
2480            Self::Store(error) => error.diagnostic_code(),
2481            Self::Query(error) => error.diagnostic_code(),
2482            Self::Recovery(error) => error.diagnostic_code(),
2483        }
2484    }
2485
2486    /// Return compact structured diagnostic detail when the payload carries one.
2487    #[must_use]
2488    pub const fn diagnostic_detail(&self) -> Option<diagnostic_code::DiagnosticDetail> {
2489        match self {
2490            Self::DiagnosticFacts(detail) => detail.diagnostic.detail().copied(),
2491            Self::Executor(error) => error.diagnostic_detail(),
2492            Self::Store(error) => error.diagnostic_detail(),
2493            Self::Query(error) => error.diagnostic_detail(),
2494            Self::Recovery(error) => error.diagnostic_detail(),
2495        }
2496    }
2497
2498    /// Project safe typed detail into canonical public numeric facts.
2499    #[must_use]
2500    #[cold]
2501    #[inline(never)]
2502    pub fn diagnostic_facts(&self) -> Vec<(diagnostic_code::DiagnosticFactTag, u64)> {
2503        match self {
2504            Self::DiagnosticFacts(detail) => detail.facts.clone(),
2505            Self::Executor(error) => error.diagnostic_facts(),
2506            Self::Query(error) => error.diagnostic_facts(),
2507            Self::Recovery(error) => error.diagnostic_facts(),
2508            Self::Store(_) => Vec::new(),
2509        }
2510    }
2511}
2512
2513impl ExecutorErrorDetail {
2514    /// Return the compact diagnostic code for this executor detail.
2515    #[must_use]
2516    pub const fn diagnostic_code(&self) -> diagnostic_code::DiagnosticCode {
2517        match self {
2518            Self::MutationRequiredFieldMissing
2519            | Self::MutationDatabaseOwnedFieldExplicit
2520            | Self::MutationBatchEmpty
2521            | Self::MutationBatchTooManyItems
2522            | Self::MutationBatchStagedBytesExceeded
2523            | Self::MutationBatchResultBytesExceeded => {
2524                diagnostic_code::DiagnosticCode::RuntimeUnsupported
2525            }
2526            Self::MutationBatchEntityMismatch | Self::MutationBatchDuplicateKey => {
2527                diagnostic_code::DiagnosticCode::RuntimeConflict
2528            }
2529            Self::MutationManagedTimestampRegression => {
2530                diagnostic_code::DiagnosticCode::RuntimeInvariantViolation
2531            }
2532            Self::AcceptedRowConstraintProgramCorrupt => {
2533                diagnostic_code::DiagnosticCode::RuntimeCorruption
2534            }
2535        }
2536    }
2537
2538    /// Return compact structured diagnostic detail for this executor detail.
2539    #[must_use]
2540    pub const fn diagnostic_detail(&self) -> Option<diagnostic_code::DiagnosticDetail> {
2541        match self {
2542            Self::MutationRequiredFieldMissing => {
2543                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2544                    boundary: diagnostic_code::RuntimeBoundaryCode::MutationRequiredFieldMissing,
2545                })
2546            }
2547            Self::MutationDatabaseOwnedFieldExplicit => {
2548                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2549                    boundary:
2550                        diagnostic_code::RuntimeBoundaryCode::MutationDatabaseOwnedFieldExplicit,
2551                })
2552            }
2553            Self::MutationBatchEmpty => Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2554                boundary: diagnostic_code::RuntimeBoundaryCode::MutationBatchEmpty,
2555            }),
2556            Self::MutationBatchTooManyItems => {
2557                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2558                    boundary: diagnostic_code::RuntimeBoundaryCode::MutationBatchTooManyItems,
2559                })
2560            }
2561            Self::MutationBatchStagedBytesExceeded => {
2562                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2563                    boundary:
2564                        diagnostic_code::RuntimeBoundaryCode::MutationBatchStagedBytesExceeded,
2565                })
2566            }
2567            Self::MutationBatchResultBytesExceeded => {
2568                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2569                    boundary:
2570                        diagnostic_code::RuntimeBoundaryCode::MutationBatchResultBytesExceeded,
2571                })
2572            }
2573            Self::MutationBatchEntityMismatch => {
2574                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2575                    boundary: diagnostic_code::RuntimeBoundaryCode::MutationBatchEntityMismatch,
2576                })
2577            }
2578            Self::MutationBatchDuplicateKey => {
2579                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2580                    boundary: diagnostic_code::RuntimeBoundaryCode::MutationBatchDuplicateKey,
2581                })
2582            }
2583            Self::MutationManagedTimestampRegression => {
2584                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2585                    boundary:
2586                        diagnostic_code::RuntimeBoundaryCode::MutationManagedTimestampRegression,
2587                })
2588            }
2589            Self::AcceptedRowConstraintProgramCorrupt => {
2590                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2591                    boundary:
2592                        diagnostic_code::RuntimeBoundaryCode::AcceptedRowConstraintProgramCorrupt,
2593                })
2594            }
2595        }
2596    }
2597
2598    /// Project safe mutation detail into canonical public numeric facts.
2599    #[must_use]
2600    #[cold]
2601    #[inline(never)]
2602    pub const fn diagnostic_facts(&self) -> Vec<(diagnostic_code::DiagnosticFactTag, u64)> {
2603        Vec::new()
2604    }
2605}
2606
2607impl RecoveryErrorDetail {
2608    /// Return the compact diagnostic code for this recovery detail.
2609    #[must_use]
2610    pub const fn diagnostic_code(&self) -> diagnostic_code::DiagnosticCode {
2611        match self {
2612            Self::UnsupportedFormatVersion { .. } => {
2613                diagnostic_code::DiagnosticCode::RuntimeIncompatiblePersistedFormat
2614            }
2615            Self::MalformedFormatMarker { .. } => {
2616                diagnostic_code::DiagnosticCode::RuntimeCorruption
2617            }
2618        }
2619    }
2620
2621    /// Return compact structured diagnostic detail for this recovery detail.
2622    #[must_use]
2623    pub const fn diagnostic_detail(&self) -> Option<diagnostic_code::DiagnosticDetail> {
2624        let kind = match self {
2625            Self::UnsupportedFormatVersion { .. } => {
2626                diagnostic_code::RuntimeErrorKind::IncompatiblePersistedFormat
2627            }
2628            Self::MalformedFormatMarker { .. } => diagnostic_code::RuntimeErrorKind::Corruption,
2629        };
2630
2631        Some(diagnostic_code::DiagnosticDetail::RuntimeKind { kind })
2632    }
2633
2634    /// Project database-format recovery context without retaining marker bytes.
2635    #[must_use]
2636    pub fn diagnostic_facts(&self) -> Vec<(diagnostic_code::DiagnosticFactTag, u64)> {
2637        match self {
2638            Self::UnsupportedFormatVersion { found, required } => {
2639                let mut facts = Vec::with_capacity(usize::from(found.is_some()) + 1);
2640                facts.push((
2641                    diagnostic_code::DiagnosticFactTag::ExpectedVersion,
2642                    u64::from(*required),
2643                ));
2644                if let Some(found) = found {
2645                    facts.push((
2646                        diagnostic_code::DiagnosticFactTag::ActualVersion,
2647                        u64::from(*found),
2648                    ));
2649                }
2650                facts
2651            }
2652            Self::MalformedFormatMarker { reason } => vec![(
2653                diagnostic_code::DiagnosticFactTag::DecodeReason,
2654                reason.diagnostic_decode_reason().raw(),
2655            )],
2656        }
2657    }
2658}
2659
2660impl StoreError {
2661    /// Return the compact diagnostic code for this store detail.
2662    #[must_use]
2663    pub const fn diagnostic_code(&self) -> diagnostic_code::DiagnosticCode {
2664        match self {
2665            Self::NotFound => diagnostic_code::DiagnosticCode::StoreNotFound,
2666            Self::Corrupt => diagnostic_code::DiagnosticCode::StoreCorruption,
2667            Self::InvariantViolation => diagnostic_code::DiagnosticCode::StoreInvariantViolation,
2668            Self::SchemaDdlPublicationRaceLost
2669            | Self::SchemaDdlRewriteRequiresMigration
2670            | Self::SchemaRowLayoutVersionExhausted
2671            | Self::SchemaTransitionBudgetExceeded { .. } => {
2672                diagnostic_code::DiagnosticCode::SchemaDdlAdmission
2673            }
2674            Self::JournalMutationRevisionExhausted | Self::SchemaGeneratedFieldAfterDdlField => {
2675                diagnostic_code::DiagnosticCode::RuntimeUnsupported
2676            }
2677            Self::SchemaGeneratedConstraintActivationStale => {
2678                diagnostic_code::DiagnosticCode::RuntimeConflict
2679            }
2680            Self::SchemaMigration { reason } => reason.diagnostic_code(),
2681        }
2682    }
2683
2684    /// Return compact structured diagnostic detail when the store error has one.
2685    #[must_use]
2686    pub const fn diagnostic_detail(&self) -> Option<diagnostic_code::DiagnosticDetail> {
2687        match self {
2688            Self::SchemaDdlPublicationRaceLost => {
2689                Some(diagnostic_code::DiagnosticDetail::SchemaDdlAdmission {
2690                    reason: diagnostic_code::SchemaDdlAdmissionCode::PublicationRaceLost,
2691                })
2692            }
2693            Self::SchemaDdlRewriteRequiresMigration => {
2694                Some(diagnostic_code::DiagnosticDetail::SchemaDdlAdmission {
2695                    reason: diagnostic_code::SchemaDdlAdmissionCode::SchemaRewriteRequiresMigration,
2696                })
2697            }
2698            Self::SchemaMigration { reason } => {
2699                Some(diagnostic_code::DiagnosticDetail::SchemaMigration { reason: *reason })
2700            }
2701            Self::SchemaRowLayoutVersionExhausted => {
2702                Some(diagnostic_code::DiagnosticDetail::SchemaDdlAdmission {
2703                    reason: diagnostic_code::SchemaDdlAdmissionCode::RowLayoutVersionExhausted,
2704                })
2705            }
2706            Self::JournalMutationRevisionExhausted => {
2707                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2708                    boundary:
2709                        diagnostic_code::RuntimeBoundaryCode::JournalMutationRevisionExhausted,
2710                })
2711            }
2712            Self::SchemaTransitionBudgetExceeded { .. } => {
2713                Some(diagnostic_code::DiagnosticDetail::SchemaDdlAdmission {
2714                    reason: diagnostic_code::SchemaDdlAdmissionCode::SchemaTransitionBudgetExceeded,
2715                })
2716            }
2717            Self::SchemaGeneratedFieldAfterDdlField => {
2718                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2719                    boundary: diagnostic_code::RuntimeBoundaryCode::GeneratedFieldAfterDdlField,
2720                })
2721            }
2722            Self::SchemaGeneratedConstraintActivationStale => {
2723                Some(diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2724                    boundary:
2725                        diagnostic_code::RuntimeBoundaryCode::GeneratedConstraintActivationStale,
2726                })
2727            }
2728            Self::NotFound | Self::Corrupt | Self::InvariantViolation => None,
2729        }
2730    }
2731}
2732
2733impl QueryErrorDetail {
2734    /// Return the compact diagnostic code for this query detail.
2735    #[must_use]
2736    pub const fn diagnostic_code(&self) -> diagnostic_code::DiagnosticCode {
2737        match self {
2738            Self::NumericOverflow => diagnostic_code::DiagnosticCode::QueryNumericOverflow,
2739            Self::NumericNotRepresentable => {
2740                diagnostic_code::DiagnosticCode::QueryNumericNotRepresentable
2741            }
2742            Self::UnsupportedSqlFeature { .. } => {
2743                diagnostic_code::DiagnosticCode::QueryUnsupportedSqlFeature
2744            }
2745            Self::SqlLowering { .. } => diagnostic_code::DiagnosticCode::QueryUnsupportedSqlFeature,
2746            Self::UnsupportedProjection { .. } => {
2747                diagnostic_code::DiagnosticCode::QueryUnsupportedProjection
2748            }
2749            Self::UnknownAggregateTargetField => {
2750                diagnostic_code::DiagnosticCode::QueryUnknownAggregateTargetField
2751            }
2752            Self::ResultShapeMismatch { .. } => {
2753                diagnostic_code::DiagnosticCode::QueryResultShapeMismatch
2754            }
2755            Self::QueryReadAdmission { .. } => diagnostic_code::DiagnosticCode::QueryReadAdmission,
2756            Self::SqlSurfaceMismatch { .. } => {
2757                diagnostic_code::DiagnosticCode::QuerySqlSurfaceMismatch
2758            }
2759            Self::SqlWriteBoundary { .. } => diagnostic_code::DiagnosticCode::QuerySqlWriteBoundary,
2760            Self::SchemaDdlAdmission { .. } => diagnostic_code::DiagnosticCode::SchemaDdlAdmission,
2761            Self::StaleSchemaRevision => diagnostic_code::DiagnosticCode::RuntimeConflict,
2762        }
2763    }
2764
2765    /// Return compact structured diagnostic detail when the query detail has one.
2766    #[must_use]
2767    pub const fn diagnostic_detail(&self) -> Option<diagnostic_code::DiagnosticDetail> {
2768        match self {
2769            Self::UnsupportedSqlFeature { feature } => {
2770                Some(diagnostic_code::DiagnosticDetail::UnsupportedSqlFeature { feature: *feature })
2771            }
2772            Self::SqlLowering { reason } => {
2773                Some(diagnostic_code::DiagnosticDetail::SqlLowering { reason: *reason })
2774            }
2775            Self::UnsupportedProjection { reason } => {
2776                Some(diagnostic_code::DiagnosticDetail::QueryProjection { reason: *reason })
2777            }
2778            Self::ResultShapeMismatch { reason } => {
2779                Some(diagnostic_code::DiagnosticDetail::QueryResultShape { reason: *reason })
2780            }
2781            Self::QueryReadAdmission { reason } => {
2782                Some(diagnostic_code::DiagnosticDetail::QueryReadAdmission { reason: *reason })
2783            }
2784            Self::SqlSurfaceMismatch { mismatch } => {
2785                Some(diagnostic_code::DiagnosticDetail::SqlSurfaceMismatch {
2786                    mismatch: *mismatch,
2787                })
2788            }
2789            Self::SqlWriteBoundary { boundary } => {
2790                Some(diagnostic_code::DiagnosticDetail::SqlWriteBoundary {
2791                    boundary: *boundary,
2792                })
2793            }
2794            Self::SchemaDdlAdmission { error } => {
2795                Some(diagnostic_code::DiagnosticDetail::SchemaDdlAdmission {
2796                    reason: error.diagnostic_code(),
2797                })
2798            }
2799            Self::NumericOverflow
2800            | Self::NumericNotRepresentable
2801            | Self::UnknownAggregateTargetField
2802            | Self::StaleSchemaRevision => None,
2803        }
2804    }
2805
2806    /// Project safe query detail into canonical public numeric facts.
2807    #[must_use]
2808    #[cold]
2809    #[inline(never)]
2810    pub const fn diagnostic_facts(&self) -> Vec<(diagnostic_code::DiagnosticFactTag, u64)> {
2811        Vec::new()
2812    }
2813}
2814
2815impl SchemaDdlAdmissionError {
2816    /// Return the compact diagnostic code for this SQL DDL admission reason.
2817    #[must_use]
2818    pub const fn diagnostic_code(&self) -> diagnostic_code::SchemaDdlAdmissionCode {
2819        match self {
2820            Self::MissingExpectedSchemaVersion => {
2821                diagnostic_code::SchemaDdlAdmissionCode::MissingExpectedSchemaVersion
2822            }
2823            Self::MissingNextSchemaVersion => {
2824                diagnostic_code::SchemaDdlAdmissionCode::MissingNextSchemaVersion
2825            }
2826            Self::StaleExpectedSchemaVersion => {
2827                diagnostic_code::SchemaDdlAdmissionCode::StaleExpectedSchemaVersion
2828            }
2829            Self::InvalidExpectedSchemaVersion => {
2830                diagnostic_code::SchemaDdlAdmissionCode::InvalidExpectedSchemaVersion
2831            }
2832            Self::InvalidNextSchemaVersion => {
2833                diagnostic_code::SchemaDdlAdmissionCode::InvalidNextSchemaVersion
2834            }
2835            Self::AcceptedSchemaChangeWithoutVersionBump => {
2836                diagnostic_code::SchemaDdlAdmissionCode::AcceptedSchemaChangeWithoutVersionBump
2837            }
2838            Self::EmptyVersionBump => diagnostic_code::SchemaDdlAdmissionCode::EmptyVersionBump,
2839            Self::VersionGap => diagnostic_code::SchemaDdlAdmissionCode::VersionGap,
2840            Self::VersionRollback => diagnostic_code::SchemaDdlAdmissionCode::VersionRollback,
2841            Self::FingerprintMethodMismatch => {
2842                diagnostic_code::SchemaDdlAdmissionCode::FingerprintMethodMismatch
2843            }
2844            Self::UnsupportedTransitionClass => {
2845                diagnostic_code::SchemaDdlAdmissionCode::UnsupportedTransitionClass
2846            }
2847            Self::PhysicalRunnerMissing => {
2848                diagnostic_code::SchemaDdlAdmissionCode::PhysicalRunnerMissing
2849            }
2850            Self::ValidationFailed => diagnostic_code::SchemaDdlAdmissionCode::ValidationFailed,
2851            Self::PublicationRaceLost => {
2852                diagnostic_code::SchemaDdlAdmissionCode::PublicationRaceLost
2853            }
2854            Self::InvalidAddColumnDefault => {
2855                diagnostic_code::SchemaDdlAdmissionCode::InvalidAddColumnDefault
2856            }
2857            Self::InvalidAlterColumnDefault => {
2858                diagnostic_code::SchemaDdlAdmissionCode::InvalidAlterColumnDefault
2859            }
2860            Self::GeneratedIndexDropRejected => {
2861                diagnostic_code::SchemaDdlAdmissionCode::GeneratedIndexDropRejected
2862            }
2863            Self::SchemaRewriteRequiresMigration => {
2864                diagnostic_code::SchemaDdlAdmissionCode::SchemaRewriteRequiresMigration
2865            }
2866            Self::SchemaTransitionBudgetExceeded { .. } => {
2867                diagnostic_code::SchemaDdlAdmissionCode::SchemaTransitionBudgetExceeded
2868            }
2869            Self::GeneratedFieldDefaultChangeRejected => {
2870                diagnostic_code::SchemaDdlAdmissionCode::GeneratedFieldDefaultChangeRejected
2871            }
2872            Self::GeneratedFieldNullabilityChangeRejected => {
2873                diagnostic_code::SchemaDdlAdmissionCode::GeneratedFieldNullabilityChangeRejected
2874            }
2875            Self::RowLayoutVersionExhausted => {
2876                diagnostic_code::SchemaDdlAdmissionCode::RowLayoutVersionExhausted
2877            }
2878        }
2879    }
2880}
2881
2882///
2883/// ErrorClass
2884/// Internal error taxonomy for runtime classification.
2885/// Not a stable API; may change without notice.
2886///
2887
2888#[repr(u8)]
2889#[derive(Clone, Copy, Eq, PartialEq)]
2890pub enum ErrorClass {
2891    Corruption,
2892    IncompatiblePersistedFormat,
2893    NotFound,
2894    Internal,
2895    Conflict,
2896    Unsupported,
2897    InvariantViolation,
2898}
2899
2900impl ErrorClass {
2901    /// Return a compact diagnostic code for this broad class and origin pair.
2902    #[must_use]
2903    pub const fn diagnostic_code(self, origin: ErrorOrigin) -> diagnostic_code::DiagnosticCode {
2904        match self {
2905            Self::Corruption if matches!(origin, ErrorOrigin::Store) => {
2906                diagnostic_code::DiagnosticCode::StoreCorruption
2907            }
2908            Self::Corruption => diagnostic_code::DiagnosticCode::RuntimeCorruption,
2909            Self::IncompatiblePersistedFormat => {
2910                diagnostic_code::DiagnosticCode::RuntimeIncompatiblePersistedFormat
2911            }
2912            Self::NotFound if matches!(origin, ErrorOrigin::Store) => {
2913                diagnostic_code::DiagnosticCode::StoreNotFound
2914            }
2915            Self::NotFound => diagnostic_code::DiagnosticCode::RuntimeNotFound,
2916            Self::Internal => diagnostic_code::DiagnosticCode::RuntimeInternal,
2917            Self::Conflict => diagnostic_code::DiagnosticCode::RuntimeConflict,
2918            Self::Unsupported if matches!(origin, ErrorOrigin::Cursor) => {
2919                diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor
2920            }
2921            Self::Unsupported => diagnostic_code::DiagnosticCode::RuntimeUnsupported,
2922            Self::InvariantViolation if matches!(origin, ErrorOrigin::Store) => {
2923                diagnostic_code::DiagnosticCode::StoreInvariantViolation
2924            }
2925            Self::InvariantViolation => diagnostic_code::DiagnosticCode::RuntimeInvariantViolation,
2926        }
2927    }
2928}
2929
2930impl fmt::Debug for ErrorClass {
2931    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2932        write!(f, "{}", *self as u8)
2933    }
2934}
2935
2936///
2937/// ErrorOrigin
2938/// Internal origin taxonomy for runtime classification.
2939/// Not a stable API; may change without notice.
2940///
2941
2942#[repr(u8)]
2943#[derive(Clone, Copy, Eq, PartialEq)]
2944pub enum ErrorOrigin {
2945    Serialize,
2946    Store,
2947    Index,
2948    Identity,
2949    Query,
2950    Planner,
2951    Cursor,
2952    Recovery,
2953    Response,
2954    Executor,
2955    Interface,
2956}
2957
2958impl ErrorOrigin {
2959    /// Return the compact diagnostic origin for this internal origin.
2960    #[must_use]
2961    pub const fn diagnostic_origin(self) -> diagnostic_code::ErrorOrigin {
2962        match self {
2963            Self::Serialize => diagnostic_code::ErrorOrigin::Serialize,
2964            Self::Store => diagnostic_code::ErrorOrigin::Store,
2965            Self::Index => diagnostic_code::ErrorOrigin::Index,
2966            Self::Identity => diagnostic_code::ErrorOrigin::Identity,
2967            Self::Query => diagnostic_code::ErrorOrigin::Query,
2968            Self::Planner => diagnostic_code::ErrorOrigin::Planner,
2969            Self::Cursor => diagnostic_code::ErrorOrigin::Cursor,
2970            Self::Recovery => diagnostic_code::ErrorOrigin::Recovery,
2971            Self::Response => diagnostic_code::ErrorOrigin::Response,
2972            Self::Executor => diagnostic_code::ErrorOrigin::Executor,
2973            Self::Interface => diagnostic_code::ErrorOrigin::Interface,
2974        }
2975    }
2976}
2977
2978impl fmt::Debug for ErrorOrigin {
2979    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2980        write!(f, "{}", *self as u8)
2981    }
2982}