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