1use std::fmt;
8
9use crate::ErrorCode;
10
11pub const MAX_PUBLIC_DIAGNOSTIC_FACTS: usize = 80;
13
14macro_rules! define_fact_tag_registry {
15 ($($name:ident = $raw:literal;)+) => {
16 #[derive(Clone, Copy, Eq, Hash, PartialEq)]
18 pub enum DiagnosticFactTag {
19 $(
20 #[doc = concat!("Public fact tag ", stringify!($raw), ".")]
21 $name,
22 )+
23 }
24
25 impl DiagnosticFactTag {
26 #[must_use]
28 pub const fn raw(self) -> u8 {
29 match self {
30 $(Self::$name => $raw,)+
31 }
32 }
33
34 #[must_use]
36 pub const fn known(raw: u8) -> Option<Self> {
37 match raw {
38 $($raw => Some(Self::$name),)+
39 _ => None,
40 }
41 }
42 }
43
44 #[cfg(test)]
45 const ORDERED_FACT_TAGS: &[DiagnosticFactTag] = &[
46 $(DiagnosticFactTag::$name,)+
47 ];
48 };
49}
50
51define_fact_tag_registry! {
54 AcceptedSchemaFingerprintMethod = 1;
55 AcceptedSchemaFingerprintHigh = 2;
56 AcceptedSchemaFingerprintLow = 3;
57 ExpectedFingerprintPrefix = 4;
58 ActualFingerprintPrefix = 5;
59 EntityTag = 6;
60 ExpectedEntityTag = 7;
61 ActualEntityTag = 8;
62 ConstraintId = 9;
63 FieldId = 10;
64 IndexId = 11;
65 RelationId = 12;
66 MutationOperation = 13;
67 RowOperation = 14;
68 BatchPosition = 15;
69 FirstBatchPosition = 16;
70 DuplicateBatchPosition = 17;
71 ClauseIndex = 18;
72 TermIndex = 19;
73 FirstTermIndex = 20;
74 DuplicateTermIndex = 21;
75 ProjectionIndex = 22;
76 GroupIndex = 23;
77 AggregateIndex = 24;
78 ArgumentIndex = 25;
79 BranchIndex = 26;
80 ComponentIndex = 27;
81 ParameterIndex = 28;
82 SourceSpanStart = 29;
83 SourceSpanEnd = 30;
84 Expected = 31;
85 Actual = 32;
86 Minimum = 33;
87 Maximum = 34;
88 Limit = 35;
89 ExpectedCount = 36;
90 ActualCount = 37;
91 ExpectedRevision = 38;
92 ActualRevision = 39;
93 CurrentRevision = 40;
94 RequestedRevision = 41;
95 ExpectedVersion = 42;
96 ActualVersion = 43;
97 CurrentVersion = 44;
98 RequestedVersion = 45;
99 ExpectedOffset = 46;
100 ActualOffset = 47;
101 ExpectedArity = 48;
102 ActualArity = 49;
103 ExpectedLength = 50;
104 ActualLength = 51;
105 ExpectedSlotCount = 52;
106 ActualSlotCount = 53;
107 RowLayout = 54;
108 HistoryFloor = 55;
109 CurrentLayout = 56;
110 PhysicalSlot = 57;
111 PhysicalGeneration = 58;
112 ExpectedMemoryId = 59;
113 ActualMemoryId = 60;
114 ConstraintKind = 61;
115 ConstraintContext = 62;
116 FieldKind = 63;
117 ValueKind = 64;
118 TypeFamily = 65;
119 FunctionKind = 66;
120 OperatorKind = 67;
121 AggregateKind = 68;
122 KeyNamespaceKind = 69;
123 ComponentKind = 70;
124 MismatchKind = 71;
125 DecodeReason = 72;
126 BudgetResource = 73;
127 MigrationPhase = 74;
128 DatabaseControlRecordKind = 75;
129 StateKind = 76;
130 PayloadComponent = 77;
131 ExpectedSignaturePrefix = 78;
132 ActualSignaturePrefix = 79;
133 FindingPosition = 80;
134 RootField = 81;
135 RecordMember = 82;
136 TupleElement = 83;
137 Newtype = 84;
138 EnumVariant = 85;
139 ListElement = 86;
140 SetElement = 87;
141 MapEntryKey = 88;
142 MapEntryValue = 89;
143 ExecutionBudgetScope = 90;
144 ExecutionLane = 91;
145 QueryShapeFingerprintPrefix = 92;
146 BacklogResource = 93;
147 CurrentCount = 94;
148 ProposedCount = 95;
149}
150
151#[derive(Clone, Copy, Eq, Hash, PartialEq)]
156pub enum DiagnosticDecodeReason {
157 CursorEmpty,
158 CursorTooLong,
159 CursorInvalidLength,
160 CursorInvalidBase64,
161 CursorGroupedDirectionMismatch,
162 CursorTokenEncode,
163 CursorTokenDecode,
164 RecoveryMarkerMagic,
165 RecoveryMarkerChecksum,
166 RecoveryMarkerState,
167}
168
169#[derive(Clone, Copy, Eq, Hash, PartialEq)]
171pub enum DiagnosticMutationOperation {
172 Insert,
173 Replace,
174 Update,
175 Delete,
176}
177
178macro_rules! define_numeric_fact_value_registry {
179 (
180 $(#[$enum_meta:meta])*
181 pub enum $name:ident {
182 $($variant:ident = $raw:literal;)+
183 }
184 ) => {
185 $(#[$enum_meta])*
186 #[derive(Clone, Copy, Eq, Hash, PartialEq)]
187 pub enum $name {
188 $(
189 #[doc = concat!("Compact diagnostic value ", stringify!($raw), ".")]
190 $variant,
191 )+
192 }
193
194 impl $name {
195 #[must_use]
197 pub const fn raw(self) -> u64 {
198 match self {
199 $(Self::$variant => $raw,)+
200 }
201 }
202
203 #[must_use]
205 pub const fn known(raw: u64) -> Option<Self> {
206 match raw {
207 $($raw => Some(Self::$variant),)+
208 _ => None,
209 }
210 }
211 }
212
213 impl fmt::Debug for $name {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 write!(f, "{}", self.raw())
216 }
217 }
218 };
219}
220
221define_numeric_fact_value_registry! {
222 pub enum DiagnosticBacklogResource {
224 Batches = 1;
225 Records = 2;
226 EncodedBytes = 3;
227 }
228}
229
230define_numeric_fact_value_registry! {
231 pub enum DiagnosticExecutionBudgetResource {
233 QueryExecutions = 1;
234 PlanningSteps = 2;
235 PlanCompilations = 3;
236 KeyIndexEntriesVisited = 4;
237 RowsVisited = 5;
238 StoredBytesRead = 6;
239 PredicateExpressionSteps = 7;
240 NestedValueSteps = 8;
241 DecodedBytes = 9;
242 MaterializedBytes = 10;
243 SortEntries = 11;
244 SortComparisons = 12;
245 SortTemporaryBytes = 13;
246 GroupDistinctEntries = 14;
247 GroupDistinctStateBytes = 15;
248 CursorSteps = 16;
249 TemporaryBytes = 17;
250 ResultRows = 18;
251 ResultBytes = 19;
252 InstructionUnits = 20;
253 }
254}
255
256impl DiagnosticExecutionBudgetResource {
257 pub const ALL: [Self; 20] = [
259 Self::QueryExecutions,
260 Self::PlanningSteps,
261 Self::PlanCompilations,
262 Self::KeyIndexEntriesVisited,
263 Self::RowsVisited,
264 Self::StoredBytesRead,
265 Self::PredicateExpressionSteps,
266 Self::NestedValueSteps,
267 Self::DecodedBytes,
268 Self::MaterializedBytes,
269 Self::SortEntries,
270 Self::SortComparisons,
271 Self::SortTemporaryBytes,
272 Self::GroupDistinctEntries,
273 Self::GroupDistinctStateBytes,
274 Self::CursorSteps,
275 Self::TemporaryBytes,
276 Self::ResultRows,
277 Self::ResultBytes,
278 Self::InstructionUnits,
279 ];
280}
281
282define_numeric_fact_value_registry! {
283 pub enum DiagnosticExecutionBudgetScope {
285 Execution = 1;
286 Request = 2;
287 }
288}
289
290define_numeric_fact_value_registry! {
291 pub enum DiagnosticExecutionLane {
293 PublicRead = 1;
294 TrustedRead = 2;
295 Diagnostic = 3;
296 Mutation = 4;
297 Recovery = 5;
298 }
299}
300
301define_numeric_fact_value_registry! {
302 pub enum DiagnosticConstraintKind {
304 Check = 1;
305 NotNull = 2;
306 Relation = 3;
307 TargetedRule = 4;
308 Unique = 5;
309 }
310}
311
312define_numeric_fact_value_registry! {
313 pub enum DiagnosticConstraintContext {
315 Integrity = 1;
316 MigrationValidation = 2;
317 WriteAdmission = 3;
318 }
319}
320
321define_numeric_fact_value_registry! {
322 pub enum DiagnosticComponentKind {
324 CommitDataKey = 1;
325 IndexKey = 2;
326 IndexKeyComponent = 3;
327 RelationTargetPrimaryKey = 4;
328 }
329}
330
331define_numeric_fact_value_registry! {
332 pub enum DiagnosticTypeFamily {
334 Blob = 1;
335 Bool = 2;
336 Collection = 3;
337 Null = 4;
338 Numeric = 5;
339 Opaque = 6;
340 Structured = 7;
341 Text = 8;
342 Unknown = 9;
343 }
344}
345
346define_numeric_fact_value_registry! {
347 pub enum DiagnosticFunctionKind {
349 Abs = 1;
350 Cbrt = 2;
351 Ceiling = 3;
352 Coalesce = 4;
353 CollectionContains = 5;
354 Contains = 6;
355 EndsWith = 7;
356 Exp = 8;
357 Floor = 9;
358 IsEmpty = 10;
359 IsMissing = 11;
360 IsNotEmpty = 12;
361 IsNotNull = 13;
362 IsNull = 14;
363 Left = 15;
364 Length = 16;
365 Ln = 17;
366 Log = 18;
367 Log2 = 19;
368 Log10 = 20;
369 Lower = 21;
370 Ltrim = 22;
371 Mod = 23;
372 NullIf = 24;
373 OctetLength = 25;
374 Position = 26;
375 Power = 27;
376 Replace = 28;
377 Right = 29;
378 Round = 30;
379 Rtrim = 31;
380 Sign = 32;
381 Sqrt = 33;
382 StartsWith = 34;
383 Substring = 35;
384 Trim = 36;
385 Trunc = 37;
386 Upper = 38;
387 InList = 39;
388 }
389}
390
391define_numeric_fact_value_registry! {
392 pub enum DiagnosticOperatorKind {
394 Not = 1;
395 Add = 2;
396 And = 3;
397 Div = 4;
398 Eq = 5;
399 Gt = 6;
400 Gte = 7;
401 Lt = 8;
402 Lte = 9;
403 Mul = 10;
404 Ne = 11;
405 Or = 12;
406 Sub = 13;
407 In = 14;
408 NotIn = 15;
409 Contains = 16;
410 StartsWith = 17;
411 EndsWith = 18;
412 }
413}
414
415define_numeric_fact_value_registry! {
416 pub enum DiagnosticAggregateKind {
418 Count = 1;
419 Sum = 2;
420 Avg = 3;
421 Exists = 4;
422 Min = 5;
423 Max = 6;
424 First = 7;
425 Last = 8;
426 }
427}
428
429impl DiagnosticDecodeReason {
430 #[must_use]
432 pub const fn raw(self) -> u64 {
433 match self {
434 Self::CursorEmpty => 1,
435 Self::CursorTooLong => 2,
436 Self::CursorInvalidLength => 3,
437 Self::CursorInvalidBase64 => 4,
438 Self::CursorGroupedDirectionMismatch => 5,
439 Self::CursorTokenEncode => 6,
440 Self::CursorTokenDecode => 7,
441 Self::RecoveryMarkerMagic => 8,
442 Self::RecoveryMarkerChecksum => 9,
443 Self::RecoveryMarkerState => 10,
444 }
445 }
446
447 #[must_use]
449 pub const fn known(raw: u64) -> Option<Self> {
450 match raw {
451 1 => Some(Self::CursorEmpty),
452 2 => Some(Self::CursorTooLong),
453 3 => Some(Self::CursorInvalidLength),
454 4 => Some(Self::CursorInvalidBase64),
455 5 => Some(Self::CursorGroupedDirectionMismatch),
456 6 => Some(Self::CursorTokenEncode),
457 7 => Some(Self::CursorTokenDecode),
458 8 => Some(Self::RecoveryMarkerMagic),
459 9 => Some(Self::RecoveryMarkerChecksum),
460 10 => Some(Self::RecoveryMarkerState),
461 _ => None,
462 }
463 }
464}
465
466impl DiagnosticMutationOperation {
467 #[must_use]
469 pub const fn raw(self) -> u64 {
470 match self {
471 Self::Insert => 1,
472 Self::Replace => 2,
473 Self::Update => 3,
474 Self::Delete => 4,
475 }
476 }
477
478 #[must_use]
480 pub const fn known(raw: u64) -> Option<Self> {
481 match raw {
482 1 => Some(Self::Insert),
483 2 => Some(Self::Replace),
484 3 => Some(Self::Update),
485 4 => Some(Self::Delete),
486 _ => None,
487 }
488 }
489}
490
491impl fmt::Debug for DiagnosticFactTag {
492 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493 write!(f, "{}", self.raw())
494 }
495}
496
497impl fmt::Debug for DiagnosticDecodeReason {
498 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
499 write!(f, "{}", self.raw())
500 }
501}
502
503impl fmt::Debug for DiagnosticMutationOperation {
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 write!(f, "{}", self.raw())
506 }
507}
508
509#[must_use]
511pub const fn pack_u32_pair(high: u32, low: u32) -> u64 {
512 (high as u64) << 32 | low as u64
513}
514
515#[must_use]
517pub const fn unpack_u32_pair(value: u64) -> (u32, u32) {
518 let bytes = value.to_be_bytes();
519 (
520 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
521 u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
522 )
523}
524
525#[derive(Clone, Copy, Debug, Eq, PartialEq)]
529pub enum DiagnosticFactSchemaMismatch {
530 GlobalMaximumExceeded,
532 CodeMaximumExceeded,
534 InvalidSequence,
536 InvalidValue,
538}
539
540pub fn validate_known_diagnostic_fact_schema(
545 code: ErrorCode,
546 facts: &[(DiagnosticFactTag, u64)],
547) -> Result<(), DiagnosticFactSchemaMismatch> {
548 validate_diagnostic_fact_schema(code, facts.len(), |index| {
549 let (tag, value) = facts[index];
550 (tag.raw(), value)
551 })
552}
553
554pub fn validate_raw_diagnostic_fact_schema(
559 code: ErrorCode,
560 facts: &[(u8, u64)],
561) -> Result<(), DiagnosticFactSchemaMismatch> {
562 validate_diagnostic_fact_schema(code, facts.len(), |index| facts[index])
563}
564
565#[expect(
566 clippy::too_many_lines,
567 reason = "the frozen E-code schema registry keeps every numeric owner visible in one exhaustive dispatch"
568)]
569fn validate_diagnostic_fact_schema(
570 code: ErrorCode,
571 fact_count: usize,
572 fact_at: impl Fn(usize) -> (u8, u64),
573) -> Result<(), DiagnosticFactSchemaMismatch> {
574 if fact_count > MAX_PUBLIC_DIAGNOSTIC_FACTS {
575 return Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded);
576 }
577
578 let maximum = diagnostic_fact_maximum(code);
579 if fact_count > maximum {
580 return Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded);
581 }
582
583 let valid_sequence = match code.raw() {
584 3 => query_plan_schema(fact_count, &fact_at),
585 5 => cursor_schema(fact_count, &fact_at),
586 15 => store_corruption_schema(fact_count, &fact_at),
587 17 => runtime_corruption_schema(fact_count, &fact_at),
588 18 => incompatible_format_schema(fact_count, &fact_at),
589 19 => runtime_invariant_schema(fact_count, &fact_at),
590 20 => runtime_conflict_schema(fact_count, &fact_at),
591 22 => runtime_unsupported_schema(fact_count, &fact_at),
592 23 => runtime_internal_schema(fact_count, &fact_at),
593 130 => tags_match(
594 fact_count,
595 &fact_at,
596 &[
597 DiagnosticFactTag::ExpectedArity,
598 DiagnosticFactTag::ActualArity,
599 ],
600 ),
601 133 | 134 | 158 | 159 => {
602 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ProjectionIndex])
603 }
604 164 => tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ParameterIndex]),
605 166 | 224 => {
606 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::Limit])
607 || tags_match(
608 fact_count,
609 &fact_at,
610 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
611 )
612 }
613 167 | 169 | 191 | 192 | 194 | 223 | 248 | 264 => tags_match(
614 fact_count,
615 &fact_at,
616 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Limit],
617 ),
618 262 => {
619 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::Limit])
620 || tags_match(
621 fact_count,
622 &fact_at,
623 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Limit],
624 )
625 }
626 263 => tags_match(
627 fact_count,
628 &fact_at,
629 &[
630 DiagnosticFactTag::BacklogResource,
631 DiagnosticFactTag::CurrentCount,
632 DiagnosticFactTag::ProposedCount,
633 DiagnosticFactTag::Limit,
634 ],
635 ),
636 185 | 221 => mutation_schema(
637 fact_count,
638 &fact_at,
639 &[
640 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
641 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
642 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
643 DiagnosticFactTag::EntityTag,
644 DiagnosticFactTag::FieldId,
645 DiagnosticFactTag::MutationOperation,
646 ],
647 ),
648 186 => tags_match(
649 fact_count,
650 &fact_at,
651 &[
652 DiagnosticFactTag::RowLayout,
653 DiagnosticFactTag::HistoryFloor,
654 DiagnosticFactTag::CurrentLayout,
655 ],
656 ),
657 187 => tags_match(
658 fact_count,
659 &fact_at,
660 &[
661 DiagnosticFactTag::RowLayout,
662 DiagnosticFactTag::ExpectedSlotCount,
663 DiagnosticFactTag::ActualSlotCount,
664 ],
665 ),
666 190 => tags_match(
667 fact_count,
668 &fact_at,
669 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Minimum],
670 ),
671 210 => constraint_schema(fact_count, &fact_at, true),
672 212 => constraint_schema(fact_count, &fact_at, false),
673 220 => mutation_schema(
674 fact_count,
675 &fact_at,
676 &[
677 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
678 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
679 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
680 DiagnosticFactTag::EntityTag,
681 DiagnosticFactTag::MutationOperation,
682 ],
683 ),
684 222 => {
685 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ActualCount]) && fact_at(0).1 == 0
686 }
687 225 | 249 | 250 | 251 => tags_match(
688 fact_count,
689 &fact_at,
690 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
691 ),
692 252 => tags_match(
693 fact_count,
694 &fact_at,
695 &[
696 DiagnosticFactTag::BudgetResource,
697 DiagnosticFactTag::Limit,
698 DiagnosticFactTag::Actual,
699 DiagnosticFactTag::ExecutionBudgetScope,
700 DiagnosticFactTag::ExecutionLane,
701 DiagnosticFactTag::QueryShapeFingerprintPrefix,
702 ],
703 ),
704 253 => tags_match(
705 fact_count,
706 &fact_at,
707 &[
708 DiagnosticFactTag::BudgetResource,
709 DiagnosticFactTag::Limit,
710 DiagnosticFactTag::Actual,
711 ],
712 ),
713 271 | 272 => tags_match(
714 fact_count,
715 &fact_at,
716 &[DiagnosticFactTag::Limit, DiagnosticFactTag::Actual],
717 ),
718 274 => {
719 tags_match(
720 fact_count,
721 &fact_at,
722 &[DiagnosticFactTag::Expected, DiagnosticFactTag::Actual],
723 ) && (0..2).all(|index| (1..=u64::from(u16::MAX)).contains(&fact_at(index).1))
724 && fact_at(0).1 != fact_at(1).1
725 }
726 226 => tags_match(
727 fact_count,
728 &fact_at,
729 &[
730 DiagnosticFactTag::BatchPosition,
731 DiagnosticFactTag::ExpectedEntityTag,
732 DiagnosticFactTag::ActualEntityTag,
733 ],
734 ),
735 227 => tags_match(
736 fact_count,
737 &fact_at,
738 &[
739 DiagnosticFactTag::EntityTag,
740 DiagnosticFactTag::FirstBatchPosition,
741 DiagnosticFactTag::DuplicateBatchPosition,
742 ],
743 ),
744 _ => fact_count == 0,
745 };
746 if !valid_sequence {
747 return Err(DiagnosticFactSchemaMismatch::InvalidSequence);
748 }
749
750 for index in 0..fact_count {
751 let (raw_tag, value) = fact_at(index);
752 let Some(tag) = DiagnosticFactTag::known(raw_tag) else {
753 return Err(DiagnosticFactSchemaMismatch::InvalidSequence);
754 };
755 if !diagnostic_fact_value_is_valid(tag, value) {
756 return Err(DiagnosticFactSchemaMismatch::InvalidValue);
757 }
758 }
759 Ok(())
760}
761
762const fn diagnostic_fact_maximum(code: ErrorCode) -> usize {
763 match code.raw() {
764 5 | 15 | 17 | 186 | 187 | 226 | 227 | 253 => 3,
765 133 | 134 | 158 | 159 | 164 | 222 => 1,
766 18 | 20 | 130 | 166 | 167 | 169 | 190 | 191 | 192 | 194 | 223 | 224 | 225 | 248 | 249
767 | 250 | 251 | 262 | 264 | 271 | 272 | 274 => 2,
768 3 | 19 | 23 => 5,
769 22 | 220 | 252 => 6,
770 185 | 221 => 7,
771 263 => 4,
772 210 => 73,
773 212 => 9,
774 _ => 0,
775 }
776}
777
778#[expect(
779 clippy::too_many_lines,
780 reason = "the query-plan E-code deliberately owns several exact finite fact sequences"
781)]
782fn query_plan_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
783 fact_count == 0
784 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::TermIndex])
785 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ComponentIndex])
786 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::GroupIndex])
787 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ClauseIndex])
788 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateIndex])
789 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateKind])
790 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ProjectionIndex])
791 || tags_match(
792 fact_count,
793 fact_at,
794 &[
795 DiagnosticFactTag::FirstTermIndex,
796 DiagnosticFactTag::DuplicateTermIndex,
797 ],
798 )
799 || tags_match(
800 fact_count,
801 fact_at,
802 &[
803 DiagnosticFactTag::ClauseIndex,
804 DiagnosticFactTag::OperatorKind,
805 ],
806 )
807 || tags_match(
808 fact_count,
809 fact_at,
810 &[
811 DiagnosticFactTag::AggregateIndex,
812 DiagnosticFactTag::AggregateKind,
813 ],
814 )
815 || tags_match(
816 fact_count,
817 fact_at,
818 &[
819 DiagnosticFactTag::AggregateKind,
820 DiagnosticFactTag::TypeFamily,
821 ],
822 )
823 || tags_match(
824 fact_count,
825 fact_at,
826 &[
827 DiagnosticFactTag::OperatorKind,
828 DiagnosticFactTag::TypeFamily,
829 ],
830 )
831 || tags_match(
832 fact_count,
833 fact_at,
834 &[
835 DiagnosticFactTag::BranchIndex,
836 DiagnosticFactTag::TypeFamily,
837 ],
838 )
839 || tags_match(
840 fact_count,
841 fact_at,
842 &[DiagnosticFactTag::TypeFamily, DiagnosticFactTag::TypeFamily],
843 )
844 || tags_match(
845 fact_count,
846 fact_at,
847 &[
848 DiagnosticFactTag::ClauseIndex,
849 DiagnosticFactTag::AggregateIndex,
850 DiagnosticFactTag::ActualCount,
851 ],
852 )
853 || tags_match(
854 fact_count,
855 fact_at,
856 &[
857 DiagnosticFactTag::FunctionKind,
858 DiagnosticFactTag::ExpectedArity,
859 DiagnosticFactTag::ActualArity,
860 ],
861 )
862 || tags_match(
863 fact_count,
864 fact_at,
865 &[
866 DiagnosticFactTag::FunctionKind,
867 DiagnosticFactTag::ArgumentIndex,
868 DiagnosticFactTag::TypeFamily,
869 ],
870 )
871 || tags_match(
872 fact_count,
873 fact_at,
874 &[
875 DiagnosticFactTag::OperatorKind,
876 DiagnosticFactTag::TypeFamily,
877 DiagnosticFactTag::TypeFamily,
878 ],
879 )
880 || tags_match(
881 fact_count,
882 fact_at,
883 &[
884 DiagnosticFactTag::BranchIndex,
885 DiagnosticFactTag::TypeFamily,
886 DiagnosticFactTag::TypeFamily,
887 ],
888 )
889 || tags_match(
890 fact_count,
891 fact_at,
892 &[
893 DiagnosticFactTag::TypeFamily,
894 DiagnosticFactTag::BranchIndex,
895 DiagnosticFactTag::TypeFamily,
896 ],
897 )
898 || tags_match(
899 fact_count,
900 fact_at,
901 &[
902 DiagnosticFactTag::BranchIndex,
903 DiagnosticFactTag::TypeFamily,
904 DiagnosticFactTag::BranchIndex,
905 DiagnosticFactTag::TypeFamily,
906 ],
907 )
908 || tags_match(
909 fact_count,
910 fact_at,
911 &[
912 DiagnosticFactTag::FunctionKind,
913 DiagnosticFactTag::ArgumentIndex,
914 DiagnosticFactTag::TypeFamily,
915 DiagnosticFactTag::ArgumentIndex,
916 DiagnosticFactTag::TypeFamily,
917 ],
918 )
919}
920
921fn cursor_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
922 if fact_count == 0 {
923 return true;
924 }
925 if tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason]) {
926 return matches!(fact_at(0).1, 1 | 3 | 5 | 6 | 7);
927 }
928 if tags_match(
929 fact_count,
930 fact_at,
931 &[
932 DiagnosticFactTag::ActualLength,
933 DiagnosticFactTag::Maximum,
934 DiagnosticFactTag::DecodeReason,
935 ],
936 ) {
937 return fact_at(2).1 == DiagnosticDecodeReason::CursorTooLong.raw();
938 }
939 if tags_match(
940 fact_count,
941 fact_at,
942 &[
943 DiagnosticFactTag::ComponentIndex,
944 DiagnosticFactTag::DecodeReason,
945 ],
946 ) {
947 return matches!(fact_at(1).1, 4 | 6 | 7);
948 }
949 tags_match(
950 fact_count,
951 fact_at,
952 &[
953 DiagnosticFactTag::ExpectedSignaturePrefix,
954 DiagnosticFactTag::ActualSignaturePrefix,
955 ],
956 ) || tags_match(
957 fact_count,
958 fact_at,
959 &[
960 DiagnosticFactTag::ExpectedOffset,
961 DiagnosticFactTag::ActualOffset,
962 ],
963 )
964}
965
966fn store_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
967 fact_count == 0
968 || (tags_match(
969 fact_count,
970 fact_at,
971 &[
972 DiagnosticFactTag::ComponentKind,
973 DiagnosticFactTag::ActualLength,
974 DiagnosticFactTag::Limit,
975 ],
976 ) && fact_at(0).1 == DiagnosticComponentKind::CommitDataKey.raw())
977 || tags_match(
978 fact_count,
979 fact_at,
980 &[
981 DiagnosticFactTag::ExpectedEntityTag,
982 DiagnosticFactTag::ActualEntityTag,
983 ],
984 )
985}
986
987fn runtime_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
988 store_corruption_schema(fact_count, fact_at)
989 || (tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason])
990 && matches!(fact_at(0).1, 8..=10))
991}
992
993fn incompatible_format_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
994 fact_count == 0
995 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedVersion])
996 || tags_match(
997 fact_count,
998 fact_at,
999 &[
1000 DiagnosticFactTag::ExpectedVersion,
1001 DiagnosticFactTag::ActualVersion,
1002 ],
1003 )
1004}
1005
1006fn runtime_invariant_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1007 fact_count == 0
1008 || (tags_match(
1009 fact_count,
1010 fact_at,
1011 &[
1012 DiagnosticFactTag::EntityTag,
1013 DiagnosticFactTag::PhysicalGeneration,
1014 DiagnosticFactTag::ComponentKind,
1015 DiagnosticFactTag::ActualArity,
1016 DiagnosticFactTag::Maximum,
1017 ],
1018 ) && fact_at(2).1 == DiagnosticComponentKind::IndexKey.raw())
1019}
1020
1021fn runtime_conflict_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1022 fact_count == 0
1023 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedRevision])
1024 || tags_match(
1025 fact_count,
1026 fact_at,
1027 &[
1028 DiagnosticFactTag::ExpectedRevision,
1029 DiagnosticFactTag::CurrentRevision,
1030 ],
1031 )
1032}
1033
1034fn runtime_unsupported_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1035 fact_count == 0
1036 || (tags_match(
1037 fact_count,
1038 fact_at,
1039 &[
1040 DiagnosticFactTag::EntityTag,
1041 DiagnosticFactTag::PhysicalGeneration,
1042 DiagnosticFactTag::ComponentIndex,
1043 DiagnosticFactTag::ComponentKind,
1044 DiagnosticFactTag::ActualLength,
1045 DiagnosticFactTag::Limit,
1046 ],
1047 ) && fact_at(3).1 == DiagnosticComponentKind::IndexKeyComponent.raw())
1048}
1049
1050fn runtime_internal_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1051 if fact_count >= 2
1054 && fact_at(0).0 == DiagnosticFactTag::EntityTag.raw()
1055 && fact_at(1).0 == DiagnosticFactTag::RelationId.raw()
1056 {
1057 return runtime_internal_detail_schema(fact_count - 2, &|index| fact_at(index + 2));
1058 }
1059 runtime_internal_detail_schema(fact_count, fact_at)
1060}
1061
1062fn runtime_internal_detail_schema(
1063 fact_count: usize,
1064 fact_at: &impl Fn(usize) -> (u8, u64),
1065) -> bool {
1066 fact_count == 0
1067 || tags_match(
1068 fact_count,
1069 fact_at,
1070 &[
1071 DiagnosticFactTag::ExpectedMemoryId,
1072 DiagnosticFactTag::ActualMemoryId,
1073 ],
1074 )
1075 || (tags_match(
1076 fact_count,
1077 fact_at,
1078 &[
1079 DiagnosticFactTag::ComponentKind,
1080 DiagnosticFactTag::ExpectedArity,
1081 DiagnosticFactTag::ActualArity,
1082 ],
1083 ) && fact_at(0).1 == DiagnosticComponentKind::RelationTargetPrimaryKey.raw())
1084}
1085
1086fn constraint_schema(
1087 fact_count: usize,
1088 fact_at: &impl Fn(usize) -> (u8, u64),
1089 allow_targeted_path: bool,
1090) -> bool {
1091 const COMMON: &[DiagnosticFactTag] = &[
1092 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
1093 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
1094 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
1095 DiagnosticFactTag::EntityTag,
1096 DiagnosticFactTag::ConstraintId,
1097 DiagnosticFactTag::ConstraintKind,
1098 DiagnosticFactTag::ConstraintContext,
1099 ];
1100 if fact_count < COMMON.len()
1101 || !tags_prefix_matches(fact_count, fact_at, COMMON)
1102 || fact_at(6).1 != DiagnosticConstraintContext::WriteAdmission.raw()
1103 {
1104 return false;
1105 }
1106
1107 let constraint_kind = fact_at(5).1;
1108 if DiagnosticConstraintKind::known(constraint_kind).is_none() {
1109 return false;
1110 }
1111 let mut index = COMMON.len();
1112 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::MutationOperation.raw() {
1113 index += 1;
1114 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::BatchPosition.raw() {
1115 index += 1;
1116 }
1117 }
1118
1119 let path_len = fact_count - index;
1120 if path_len == 0 {
1121 return !allow_targeted_path
1122 || constraint_kind != DiagnosticConstraintKind::TargetedRule.raw();
1123 }
1124 allow_targeted_path
1125 && constraint_kind == DiagnosticConstraintKind::TargetedRule.raw()
1126 && path_len <= 64
1127 && (index..fact_count).all(|position| {
1128 DiagnosticFactTag::known(fact_at(position).0).is_some_and(is_value_path_tag)
1129 })
1130}
1131
1132fn mutation_schema(
1135 fact_count: usize,
1136 fact_at: &impl Fn(usize) -> (u8, u64),
1137 required: &[DiagnosticFactTag],
1138) -> bool {
1139 tags_prefix_matches(fact_count, fact_at, required)
1140 && (fact_count == required.len()
1141 || (fact_count == required.len() + 1
1142 && fact_at(required.len()).0 == DiagnosticFactTag::BatchPosition.raw()))
1143}
1144
1145fn tags_match(
1146 fact_count: usize,
1147 fact_at: &impl Fn(usize) -> (u8, u64),
1148 expected: &[DiagnosticFactTag],
1149) -> bool {
1150 fact_count == expected.len() && tags_prefix_matches(fact_count, fact_at, expected)
1151}
1152
1153fn tags_prefix_matches(
1154 fact_count: usize,
1155 fact_at: &impl Fn(usize) -> (u8, u64),
1156 expected: &[DiagnosticFactTag],
1157) -> bool {
1158 fact_count >= expected.len()
1159 && expected
1160 .iter()
1161 .enumerate()
1162 .all(|(index, tag)| fact_at(index).0 == tag.raw())
1163}
1164
1165const fn is_value_path_tag(tag: DiagnosticFactTag) -> bool {
1166 matches!(
1167 tag,
1168 DiagnosticFactTag::RootField
1169 | DiagnosticFactTag::RecordMember
1170 | DiagnosticFactTag::TupleElement
1171 | DiagnosticFactTag::Newtype
1172 | DiagnosticFactTag::EnumVariant
1173 | DiagnosticFactTag::ListElement
1174 | DiagnosticFactTag::SetElement
1175 | DiagnosticFactTag::MapEntryKey
1176 | DiagnosticFactTag::MapEntryValue
1177 )
1178}
1179
1180const fn diagnostic_fact_value_is_valid(tag: DiagnosticFactTag, value: u64) -> bool {
1181 match tag {
1182 DiagnosticFactTag::AcceptedSchemaFingerprintMethod
1183 | DiagnosticFactTag::ExpectedMemoryId
1184 | DiagnosticFactTag::ActualMemoryId => value <= u8::MAX as u64,
1185 DiagnosticFactTag::ConstraintId
1186 | DiagnosticFactTag::FieldId
1187 | DiagnosticFactTag::IndexId
1188 | DiagnosticFactTag::RelationId
1189 | DiagnosticFactTag::BatchPosition
1190 | DiagnosticFactTag::FirstBatchPosition
1191 | DiagnosticFactTag::DuplicateBatchPosition
1192 | DiagnosticFactTag::RowLayout
1193 | DiagnosticFactTag::HistoryFloor
1194 | DiagnosticFactTag::CurrentLayout
1195 | DiagnosticFactTag::RootField
1196 | DiagnosticFactTag::Newtype
1197 | DiagnosticFactTag::ListElement
1198 | DiagnosticFactTag::SetElement
1199 | DiagnosticFactTag::MapEntryKey
1200 | DiagnosticFactTag::MapEntryValue => value <= u32::MAX as u64,
1201 DiagnosticFactTag::ConstraintKind => DiagnosticConstraintKind::known(value).is_some(),
1202 DiagnosticFactTag::BacklogResource => DiagnosticBacklogResource::known(value).is_some(),
1203 DiagnosticFactTag::ConstraintContext => DiagnosticConstraintContext::known(value).is_some(),
1204 DiagnosticFactTag::TypeFamily => DiagnosticTypeFamily::known(value).is_some(),
1205 DiagnosticFactTag::FunctionKind => DiagnosticFunctionKind::known(value).is_some(),
1206 DiagnosticFactTag::OperatorKind => DiagnosticOperatorKind::known(value).is_some(),
1207 DiagnosticFactTag::AggregateKind => DiagnosticAggregateKind::known(value).is_some(),
1208 DiagnosticFactTag::ComponentKind => DiagnosticComponentKind::known(value).is_some(),
1209 DiagnosticFactTag::DecodeReason => DiagnosticDecodeReason::known(value).is_some(),
1210 DiagnosticFactTag::BudgetResource => {
1211 DiagnosticExecutionBudgetResource::known(value).is_some()
1212 }
1213 DiagnosticFactTag::ExecutionBudgetScope => {
1214 DiagnosticExecutionBudgetScope::known(value).is_some()
1215 }
1216 DiagnosticFactTag::ExecutionLane => DiagnosticExecutionLane::known(value).is_some(),
1217 DiagnosticFactTag::MutationOperation => DiagnosticMutationOperation::known(value).is_some(),
1218 _ => true,
1219 }
1220}
1221
1222#[cfg(test)]
1223mod tests {
1224 use super::{
1225 DiagnosticAggregateKind, DiagnosticBacklogResource, DiagnosticComponentKind,
1226 DiagnosticConstraintContext, DiagnosticConstraintKind, DiagnosticDecodeReason,
1227 DiagnosticExecutionBudgetResource, DiagnosticExecutionBudgetScope, DiagnosticExecutionLane,
1228 DiagnosticFactSchemaMismatch, DiagnosticFactTag, DiagnosticFunctionKind,
1229 DiagnosticMutationOperation, DiagnosticOperatorKind, DiagnosticTypeFamily,
1230 ORDERED_FACT_TAGS, pack_u32_pair, unpack_u32_pair, validate_known_diagnostic_fact_schema,
1231 validate_raw_diagnostic_fact_schema,
1232 };
1233 use crate::ErrorCode;
1234
1235 #[test]
1236 fn bucket_mismatch_requires_two_distinct_nonzero_page_counts() {
1237 let code = ErrorCode::RUNTIME_BOUNDARY_MEMORY_BUCKET_SIZE_MISMATCH;
1238 let expected = DiagnosticFactTag::Expected;
1239 let actual = DiagnosticFactTag::Actual;
1240 assert_eq!(
1241 validate_known_diagnostic_fact_schema(code, &[(expected, 16), (actual, 128)]),
1242 Ok(()),
1243 );
1244 for facts in [
1245 vec![],
1246 vec![(expected, 16)],
1247 vec![(actual, 128), (expected, 16)],
1248 vec![(expected, 16), (expected, 128)],
1249 vec![(expected, 16), (actual, 16)],
1250 vec![(expected, 0), (actual, 128)],
1251 vec![(expected, 16), (actual, 0)],
1252 vec![(expected, 65_536), (actual, 128)],
1253 vec![(expected, 16), (actual, 65_536)],
1254 ] {
1255 assert_eq!(
1256 validate_known_diagnostic_fact_schema(code, &facts),
1257 Err(DiagnosticFactSchemaMismatch::InvalidSequence),
1258 );
1259 }
1260 assert_eq!(
1261 validate_known_diagnostic_fact_schema(
1262 code,
1263 &[(expected, 16), (actual, 128), (actual, 128)],
1264 ),
1265 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded),
1266 );
1267 }
1268
1269 #[test]
1270 fn fact_tag_registry_is_fixed_unique_and_contiguous() {
1271 for (index, tag) in ORDERED_FACT_TAGS.iter().copied().enumerate() {
1272 let expected = u8::try_from(index + 1).expect("fact-tag index fits u8");
1273 assert_eq!(tag.raw(), expected);
1274 assert_eq!(DiagnosticFactTag::known(expected), Some(tag));
1275 }
1276
1277 assert_eq!(DiagnosticFactTag::known(0), None);
1278 assert_eq!(DiagnosticFactTag::known(96), None);
1279 assert_eq!(DiagnosticFactTag::known(u8::MAX), None);
1280 }
1281
1282 #[test]
1283 fn execution_budget_fact_value_registries_are_fixed() {
1284 assert_eq!(DiagnosticBacklogResource::Batches.raw(), 1);
1285 assert_eq!(DiagnosticBacklogResource::Records.raw(), 2);
1286 assert_eq!(DiagnosticBacklogResource::EncodedBytes.raw(), 3);
1287 assert_eq!(DiagnosticBacklogResource::known(4), None);
1288
1289 for (index, resource) in DiagnosticExecutionBudgetResource::ALL
1290 .iter()
1291 .copied()
1292 .enumerate()
1293 {
1294 let expected = u64::try_from(index + 1).expect("resource index fits u64");
1295 assert_eq!(resource.raw(), expected);
1296 assert_eq!(
1297 DiagnosticExecutionBudgetResource::known(expected),
1298 Some(resource)
1299 );
1300 }
1301 assert_eq!(DiagnosticExecutionBudgetResource::known(0), None);
1302 assert_eq!(DiagnosticExecutionBudgetResource::known(21), None);
1303
1304 assert_eq!(DiagnosticExecutionBudgetScope::Execution.raw(), 1);
1305 assert_eq!(DiagnosticExecutionBudgetScope::Request.raw(), 2);
1306 assert_eq!(DiagnosticExecutionBudgetScope::known(3), None);
1307
1308 assert_eq!(DiagnosticExecutionLane::PublicRead.raw(), 1);
1309 assert_eq!(DiagnosticExecutionLane::TrustedRead.raw(), 2);
1310 assert_eq!(DiagnosticExecutionLane::Diagnostic.raw(), 3);
1311 assert_eq!(DiagnosticExecutionLane::Mutation.raw(), 4);
1312 assert_eq!(DiagnosticExecutionLane::Recovery.raw(), 5);
1313 assert_eq!(DiagnosticExecutionLane::known(6), None);
1314 }
1315
1316 #[test]
1317 fn accepted_identity_pair_packing_is_exact() {
1318 for pair in [
1319 (0, 0),
1320 (1, 2),
1321 (u32::MAX, 0),
1322 (0, u32::MAX),
1323 (u32::MAX, u32::MAX),
1324 ] {
1325 assert_eq!(unpack_u32_pair(pack_u32_pair(pair.0, pair.1)), pair);
1326 }
1327 }
1328
1329 #[test]
1330 fn constraint_fact_value_registries_are_fixed() {
1331 assert_eq!(DiagnosticConstraintKind::Check.raw(), 1);
1332 assert_eq!(DiagnosticConstraintKind::NotNull.raw(), 2);
1333 assert_eq!(DiagnosticConstraintKind::Relation.raw(), 3);
1334 assert_eq!(DiagnosticConstraintKind::TargetedRule.raw(), 4);
1335 assert_eq!(DiagnosticConstraintKind::Unique.raw(), 5);
1336 assert_eq!(DiagnosticConstraintKind::known(0), None);
1337 assert_eq!(DiagnosticConstraintKind::known(6), None);
1338
1339 assert_eq!(DiagnosticConstraintContext::Integrity.raw(), 1);
1340 assert_eq!(DiagnosticConstraintContext::MigrationValidation.raw(), 2);
1341 assert_eq!(DiagnosticConstraintContext::WriteAdmission.raw(), 3);
1342 assert_eq!(DiagnosticConstraintContext::known(0), None);
1343 assert_eq!(DiagnosticConstraintContext::known(4), None);
1344 }
1345
1346 #[test]
1347 fn component_kind_registry_is_fixed_and_numeric() {
1348 let kinds = [
1349 DiagnosticComponentKind::CommitDataKey,
1350 DiagnosticComponentKind::IndexKey,
1351 DiagnosticComponentKind::IndexKeyComponent,
1352 DiagnosticComponentKind::RelationTargetPrimaryKey,
1353 ];
1354
1355 for (index, kind) in kinds.iter().copied().enumerate() {
1356 let expected = (index + 1) as u64;
1357 assert_eq!(kind.raw(), expected);
1358 assert_eq!(DiagnosticComponentKind::known(expected), Some(kind));
1359 assert_eq!(format!("{kind:?}"), expected.to_string());
1360 }
1361 assert_eq!(DiagnosticComponentKind::known(0), None);
1362 assert_eq!(DiagnosticComponentKind::known(5), None);
1363 }
1364
1365 #[test]
1366 fn decode_reason_registry_is_fixed_and_numeric() {
1367 let reasons = [
1368 DiagnosticDecodeReason::CursorEmpty,
1369 DiagnosticDecodeReason::CursorTooLong,
1370 DiagnosticDecodeReason::CursorInvalidLength,
1371 DiagnosticDecodeReason::CursorInvalidBase64,
1372 DiagnosticDecodeReason::CursorGroupedDirectionMismatch,
1373 DiagnosticDecodeReason::CursorTokenEncode,
1374 DiagnosticDecodeReason::CursorTokenDecode,
1375 DiagnosticDecodeReason::RecoveryMarkerMagic,
1376 DiagnosticDecodeReason::RecoveryMarkerChecksum,
1377 DiagnosticDecodeReason::RecoveryMarkerState,
1378 ];
1379
1380 for (index, reason) in reasons.iter().copied().enumerate() {
1381 let expected = (index + 1) as u64;
1382 assert_eq!(reason.raw(), expected);
1383 assert_eq!(DiagnosticDecodeReason::known(expected), Some(reason));
1384 assert_eq!(format!("{reason:?}"), expected.to_string());
1385 }
1386
1387 assert_eq!(DiagnosticDecodeReason::known(0), None);
1388 assert_eq!(DiagnosticDecodeReason::known(11), None);
1389 }
1390
1391 #[test]
1392 fn mutation_operation_registry_is_fixed_and_numeric() {
1393 let operations = [
1394 DiagnosticMutationOperation::Insert,
1395 DiagnosticMutationOperation::Replace,
1396 DiagnosticMutationOperation::Update,
1397 DiagnosticMutationOperation::Delete,
1398 ];
1399
1400 for (index, operation) in operations.iter().copied().enumerate() {
1401 let expected = (index + 1) as u64;
1402 assert_eq!(operation.raw(), expected);
1403 assert_eq!(
1404 DiagnosticMutationOperation::known(expected),
1405 Some(operation)
1406 );
1407 assert_eq!(format!("{operation:?}"), expected.to_string());
1408 }
1409
1410 assert_eq!(DiagnosticMutationOperation::known(0), None);
1411 assert_eq!(DiagnosticMutationOperation::known(5), None);
1412 }
1413
1414 #[test]
1415 fn query_kind_registries_are_fixed_contiguous_and_numeric() {
1416 for raw in 1..=9 {
1417 let value = DiagnosticTypeFamily::known(raw).expect("type family should be known");
1418 assert_eq!(value.raw(), raw);
1419 assert_eq!(format!("{value:?}"), raw.to_string());
1420 }
1421 assert_eq!(DiagnosticTypeFamily::known(0), None);
1422 assert_eq!(DiagnosticTypeFamily::known(10), None);
1423
1424 for raw in 1..=39 {
1425 let value = DiagnosticFunctionKind::known(raw).expect("function kind should be known");
1426 assert_eq!(value.raw(), raw);
1427 assert_eq!(format!("{value:?}"), raw.to_string());
1428 }
1429 assert_eq!(DiagnosticFunctionKind::known(0), None);
1430 assert_eq!(DiagnosticFunctionKind::known(40), None);
1431
1432 for raw in 1..=18 {
1433 let value = DiagnosticOperatorKind::known(raw).expect("operator kind should be known");
1434 assert_eq!(value.raw(), raw);
1435 assert_eq!(format!("{value:?}"), raw.to_string());
1436 }
1437 assert_eq!(DiagnosticOperatorKind::known(0), None);
1438 assert_eq!(DiagnosticOperatorKind::known(19), None);
1439
1440 for raw in 1..=8 {
1441 let value =
1442 DiagnosticAggregateKind::known(raw).expect("aggregate kind should be known");
1443 assert_eq!(value.raw(), raw);
1444 assert_eq!(format!("{value:?}"), raw.to_string());
1445 }
1446 assert_eq!(DiagnosticAggregateKind::known(0), None);
1447 assert_eq!(DiagnosticAggregateKind::known(9), None);
1448 }
1449
1450 #[test]
1451 fn per_code_schema_rejects_missing_disallowed_and_noncanonical_tags() {
1452 let valid = [
1453 (DiagnosticFactTag::ActualCount, 5),
1454 (DiagnosticFactTag::Limit, 4),
1455 ];
1456 assert_eq!(
1457 validate_known_diagnostic_fact_schema(
1458 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1459 &valid,
1460 ),
1461 Ok(())
1462 );
1463 assert_eq!(
1464 validate_known_diagnostic_fact_schema(
1465 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1466 &valid[..1],
1467 ),
1468 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1469 );
1470 assert_eq!(
1471 validate_known_diagnostic_fact_schema(
1472 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1473 &[valid[1], valid[0]],
1474 ),
1475 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1476 );
1477 assert_eq!(
1478 validate_known_diagnostic_fact_schema(ErrorCode::QUERY_VALIDATE, &valid),
1479 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1480 );
1481
1482 assert_eq!(
1483 validate_known_diagnostic_fact_schema(
1484 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_COMMIT_WORK_EXCEEDED,
1485 &valid,
1486 ),
1487 Ok(())
1488 );
1489 assert_eq!(
1490 validate_known_diagnostic_fact_schema(
1491 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_COMMIT_WORK_EXCEEDED,
1492 &valid[1..],
1493 ),
1494 Ok(())
1495 );
1496 }
1497
1498 #[test]
1499 fn execution_budget_schema_requires_complete_typed_attribution() {
1500 let valid = [
1501 (
1502 DiagnosticFactTag::BudgetResource,
1503 DiagnosticExecutionBudgetResource::StoredBytesRead.raw(),
1504 ),
1505 (DiagnosticFactTag::Limit, 4_096),
1506 (DiagnosticFactTag::Actual, 4_097),
1507 (
1508 DiagnosticFactTag::ExecutionBudgetScope,
1509 DiagnosticExecutionBudgetScope::Request.raw(),
1510 ),
1511 (
1512 DiagnosticFactTag::ExecutionLane,
1513 DiagnosticExecutionLane::TrustedRead.raw(),
1514 ),
1515 (DiagnosticFactTag::QueryShapeFingerprintPrefix, 17),
1516 ];
1517 assert_eq!(
1518 validate_known_diagnostic_fact_schema(
1519 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1520 &valid,
1521 ),
1522 Ok(())
1523 );
1524 assert_eq!(
1525 validate_known_diagnostic_fact_schema(
1526 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1527 &valid[..5],
1528 ),
1529 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1530 );
1531
1532 let mut invalid_resource = valid;
1533 invalid_resource[0].1 = 0;
1534 assert_eq!(
1535 validate_known_diagnostic_fact_schema(
1536 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1537 &invalid_resource,
1538 ),
1539 Err(DiagnosticFactSchemaMismatch::InvalidValue)
1540 );
1541 }
1542
1543 #[test]
1544 fn raw_schema_keeps_unknown_context_numeric_but_marks_it_invalid() {
1545 assert_eq!(
1546 validate_raw_diagnostic_fact_schema(
1547 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1548 &[(u8::MAX, 17)],
1549 ),
1550 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1551 );
1552 assert_eq!(
1553 validate_raw_diagnostic_fact_schema(
1554 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1555 &[(DiagnosticFactTag::DecodeReason.raw(), u64::MAX)],
1556 ),
1557 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1558 );
1559 }
1560
1561 #[test]
1562 fn constraint_schema_enforces_authority_operation_and_bounded_path_suffix() {
1563 let mut targeted = vec![
1564 (DiagnosticFactTag::AcceptedSchemaFingerprintMethod, 1),
1565 (DiagnosticFactTag::AcceptedSchemaFingerprintHigh, 2),
1566 (DiagnosticFactTag::AcceptedSchemaFingerprintLow, 3),
1567 (DiagnosticFactTag::EntityTag, 17),
1568 (DiagnosticFactTag::ConstraintId, 4),
1569 (
1570 DiagnosticFactTag::ConstraintKind,
1571 DiagnosticConstraintKind::TargetedRule.raw(),
1572 ),
1573 (
1574 DiagnosticFactTag::ConstraintContext,
1575 DiagnosticConstraintContext::WriteAdmission.raw(),
1576 ),
1577 (
1578 DiagnosticFactTag::MutationOperation,
1579 DiagnosticMutationOperation::Insert.raw(),
1580 ),
1581 (DiagnosticFactTag::BatchPosition, 0),
1582 ];
1583 targeted.extend((0..64).map(|index| (DiagnosticFactTag::ListElement, index)));
1584 assert_eq!(targeted.len(), 73);
1585 assert_eq!(
1586 validate_known_diagnostic_fact_schema(
1587 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1588 targeted.as_slice(),
1589 ),
1590 Ok(())
1591 );
1592
1593 let mut overlong = targeted.clone();
1594 overlong.push((DiagnosticFactTag::ListElement, 64));
1595 assert_eq!(
1596 validate_known_diagnostic_fact_schema(
1597 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1598 overlong.as_slice(),
1599 ),
1600 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1601 );
1602
1603 let mut non_targeted_path = targeted;
1604 non_targeted_path[5].1 = DiagnosticConstraintKind::Unique.raw();
1605 assert_eq!(
1606 validate_known_diagnostic_fact_schema(
1607 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1608 non_targeted_path.as_slice(),
1609 ),
1610 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1611 );
1612 }
1613
1614 #[test]
1615 fn schema_enforces_global_ceiling_before_per_code_ceiling() {
1616 let facts = vec![(DiagnosticFactTag::ActualCount.raw(), 0); 81];
1617 assert_eq!(
1618 validate_raw_diagnostic_fact_schema(ErrorCode::QUERY_PLAN, facts.as_slice()),
1619 Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded)
1620 );
1621 }
1622}