Skip to main content

icydb_core/error/
mod.rs

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