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