1use super::{
9 CandidateTraceQualificationError, CandidateTraceQualificationStatus,
10 QualifiedCandidateTraceRunProvenance,
11};
12
13pub const CANDIDATE_TRACE_FORMAT_VERSION: u32 = 3;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct CandidateTraceDigest {
25 pub first: u64,
26 pub second: u64,
27}
28
29impl CandidateTraceDigest {
30 const FIRST_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
31 const FIRST_PRIME: u64 = 0x0000_0100_0000_01b3;
32 const SECOND_OFFSET: u64 = 0x9e37_79b9_7f4a_7c15;
33 const SECOND_MULTIPLIER: u64 = 0xd6e8_feb8_6659_fd93;
34
35 pub const fn empty() -> Self {
36 Self {
37 first: Self::FIRST_OFFSET,
38 second: Self::SECOND_OFFSET,
39 }
40 }
41
42 pub fn of_bytes(bytes: &[u8]) -> Self {
43 let mut digest = Self::empty();
44 digest.update(bytes);
45 digest
46 }
47
48 pub fn update(&mut self, bytes: &[u8]) {
49 for &byte in bytes {
50 self.first ^= u64::from(byte);
51 self.first = self.first.wrapping_mul(Self::FIRST_PRIME);
52
53 self.second ^= u64::from(byte).wrapping_add(0x9d);
54 self.second = self
55 .second
56 .rotate_left(13)
57 .wrapping_mul(Self::SECOND_MULTIPLIER)
58 .wrapping_add(0x9e37_79b9);
59 }
60 }
61}
62
63impl Default for CandidateTraceDigest {
64 fn default() -> Self {
65 Self::empty()
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct CandidateTracePhasePlan {
75 pub kind: String,
76 pub attributes: Vec<CandidateTracePhaseAttribute>,
81 pub opaque: bool,
82 pub children: Vec<Self>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
87pub struct CandidateTracePhaseAttribute {
88 pub key: String,
89 pub value: String,
90}
91
92impl CandidateTracePhasePlan {
93 pub fn known<K, V, I>(kind: impl Into<String>, attributes: I, children: Vec<Self>) -> Self
94 where
95 I: IntoIterator<Item = (K, V)>,
96 K: Into<String>,
97 V: Into<String>,
98 {
99 let mut attributes = attributes
100 .into_iter()
101 .map(|(key, value)| CandidateTracePhaseAttribute {
102 key: key.into(),
103 value: value.into(),
104 })
105 .collect::<Vec<_>>();
106 attributes.sort();
107 assert!(
108 attributes.windows(2).all(|pair| pair[0].key != pair[1].key),
109 "candidate trace phase-plan attributes must have unique keys"
110 );
111 Self {
112 kind: kind.into(),
113 attributes,
114 opaque: false,
115 children,
116 }
117 }
118
119 pub fn opaque(kind: impl Into<String>) -> Self {
120 Self {
121 kind: kind.into(),
122 attributes: Vec::new(),
123 opaque: true,
124 children: Vec::new(),
125 }
126 }
127
128 pub fn is_complete(&self) -> bool {
129 !self.opaque && self.children.iter().all(Self::is_complete)
130 }
131
132 pub fn canonical_bytes(&self) -> Vec<u8> {
133 let mut out = Vec::new();
134 self.append_canonical_bytes(&mut out);
135 out
136 }
137
138 fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
139 out.push(0x50);
140 append_string(out, &self.kind);
141 append_bool(out, self.opaque);
142 append_len(out, self.attributes.len());
143 for attribute in &self.attributes {
144 append_string(out, &attribute.key);
145 append_string(out, &attribute.value);
146 }
147 append_len(out, self.children.len());
148 for child in &self.children {
149 child.append_canonical_bytes(out);
150 }
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct CandidateTraceExecutionPolicy {
165 pub kind: String,
166 pub attributes: Vec<CandidateTracePhaseAttribute>,
169 pub opaque: bool,
170}
171
172impl CandidateTraceExecutionPolicy {
173 pub fn known<K, V, I>(kind: impl Into<String>, attributes: I) -> Self
174 where
175 I: IntoIterator<Item = (K, V)>,
176 K: Into<String>,
177 V: Into<String>,
178 {
179 let mut attributes = attributes
180 .into_iter()
181 .map(|(key, value)| CandidateTracePhaseAttribute {
182 key: key.into(),
183 value: value.into(),
184 })
185 .collect::<Vec<_>>();
186 attributes.sort();
187 assert!(
188 attributes.windows(2).all(|pair| pair[0].key != pair[1].key),
189 "candidate trace execution-policy attributes must have unique keys"
190 );
191 Self {
192 kind: kind.into(),
193 attributes,
194 opaque: false,
195 }
196 }
197
198 pub fn opaque(kind: impl Into<String>) -> Self {
199 Self::opaque_with_attributes(kind, std::iter::empty::<(String, String)>())
200 }
201
202 pub fn opaque_with_attributes<K, V, I>(kind: impl Into<String>, attributes: I) -> Self
207 where
208 I: IntoIterator<Item = (K, V)>,
209 K: Into<String>,
210 V: Into<String>,
211 {
212 let mut attributes = attributes
213 .into_iter()
214 .map(|(key, value)| CandidateTracePhaseAttribute {
215 key: key.into(),
216 value: value.into(),
217 })
218 .collect::<Vec<_>>();
219 attributes.sort();
220 assert!(
221 attributes.windows(2).all(|pair| pair[0].key != pair[1].key),
222 "candidate trace execution-policy attributes must have unique keys"
223 );
224 Self {
225 kind: kind.into(),
226 attributes,
227 opaque: true,
228 }
229 }
230
231 pub const fn is_complete(&self) -> bool {
232 !self.opaque
233 }
234
235 pub fn canonical_bytes(&self) -> Vec<u8> {
236 let mut out = Vec::new();
237 out.push(0x58);
238 append_string(&mut out, &self.kind);
239 append_bool(&mut out, self.opaque);
240 append_len(&mut out, self.attributes.len());
241 for attribute in &self.attributes {
242 append_string(&mut out, &attribute.key);
243 append_string(&mut out, &attribute.value);
244 }
245 out
246 }
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
255pub struct CandidateTraceExternalDigest {
256 pub bytes: [u8; 32],
257}
258
259impl CandidateTraceExternalDigest {
260 pub const fn sha256(bytes: [u8; 32]) -> Self {
261 Self { bytes }
262 }
263
264 fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
265 out.push(1);
269 out.extend_from_slice(&self.bytes);
270 }
271}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
275pub struct CandidateTraceInputAttestation {
276 producer: String,
277}
278
279impl CandidateTraceInputAttestation {
280 pub fn external(producer: impl Into<String>) -> Self {
281 let producer = producer.into();
282 assert!(
283 !producer.is_empty(),
284 "candidate trace external provenance producer must not be empty"
285 );
286 Self { producer }
287 }
288
289 pub fn external_producer(&self) -> &str {
290 &self.producer
291 }
292
293 fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
294 out.push(1);
295 append_string(out, &self.producer);
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq)]
308pub struct CandidateTraceInputProvenance {
309 pub schema_digest: CandidateTraceExternalDigest,
310 pub instance_digest: CandidateTraceExternalDigest,
311 pub initial_state_digest: CandidateTraceExternalDigest,
312 pub core_tree_digest: Option<CandidateTraceExternalDigest>,
313 pub build_digest: Option<CandidateTraceExternalDigest>,
314 pub attestation: CandidateTraceInputAttestation,
315}
316
317impl CandidateTraceInputProvenance {
318 pub fn externally_attested(
319 schema_digest: CandidateTraceExternalDigest,
320 instance_digest: CandidateTraceExternalDigest,
321 initial_state_digest: CandidateTraceExternalDigest,
322 producer: impl Into<String>,
323 ) -> Self {
324 Self {
325 schema_digest,
326 instance_digest,
327 initial_state_digest,
328 core_tree_digest: None,
329 build_digest: None,
330 attestation: CandidateTraceInputAttestation::external(producer),
331 }
332 }
333
334 pub fn with_core_tree_digest(mut self, digest: CandidateTraceExternalDigest) -> Self {
335 self.core_tree_digest = Some(digest);
336 self
337 }
338
339 pub fn with_build_digest(mut self, digest: CandidateTraceExternalDigest) -> Self {
340 self.build_digest = Some(digest);
341 self
342 }
343
344 fn canonical_bytes(&self) -> Vec<u8> {
345 let mut out = Vec::new();
346 out.push(0x49);
347 self.schema_digest.append_canonical_bytes(&mut out);
348 self.instance_digest.append_canonical_bytes(&mut out);
349 self.initial_state_digest.append_canonical_bytes(&mut out);
350 append_optional_external_digest(&mut out, self.core_tree_digest);
351 append_optional_external_digest(&mut out, self.build_digest);
352 self.attestation.append_canonical_bytes(&mut out);
353 out
354 }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
359pub enum CandidateTraceInputProvenanceStatus {
360 Absent,
361 ExternallyAttested,
362}
363
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370pub struct CandidateTraceProvenanceStatus {
371 pub execution_policy_complete: bool,
372 pub resolved_phase_plan_complete: bool,
373 pub input_provenance: CandidateTraceInputProvenanceStatus,
374 pub qualification: CandidateTraceQualificationStatus,
375}
376
377impl CandidateTraceProvenanceStatus {
378 pub const fn has_complete_execution_plan(self) -> bool {
379 self.execution_policy_complete && self.resolved_phase_plan_complete
380 }
381}
382
383#[derive(Debug, Clone, PartialEq, Eq)]
385pub struct CandidateTraceHeader {
386 pub format_version: u32,
387 pub configured_input: String,
393 pub configured_input_digest: CandidateTraceDigest,
394 pub execution_policy: CandidateTraceExecutionPolicy,
397 pub execution_policy_digest: CandidateTraceDigest,
398 pub execution_policy_complete: bool,
401 pub input_provenance: Option<CandidateTraceInputProvenance>,
404 pub input_provenance_digest: Option<CandidateTraceDigest>,
405 pub qualified_run_provenance: Option<QualifiedCandidateTraceRunProvenance>,
409 pub resolved_phase_plan: CandidateTracePhasePlan,
410 pub resolved_phase_plan_digest: CandidateTraceDigest,
411 pub resolved_phase_plan_complete: bool,
414}
415
416impl CandidateTraceHeader {
417 pub fn new(
418 configured_input: String,
419 execution_policy: CandidateTraceExecutionPolicy,
420 resolved_phase_plan: CandidateTracePhasePlan,
421 input_provenance: Option<CandidateTraceInputProvenance>,
422 ) -> Self {
423 let configured_input_digest = CandidateTraceDigest::of_bytes(configured_input.as_bytes());
424 let execution_policy_digest =
425 CandidateTraceDigest::of_bytes(&execution_policy.canonical_bytes());
426 let execution_policy_complete = execution_policy.is_complete();
427 let input_provenance_digest = input_provenance
428 .as_ref()
429 .map(|provenance| CandidateTraceDigest::of_bytes(&provenance.canonical_bytes()));
430 let resolved_phase_plan_digest =
431 CandidateTraceDigest::of_bytes(&resolved_phase_plan.canonical_bytes());
432 let resolved_phase_plan_complete = resolved_phase_plan.is_complete();
433 Self {
434 format_version: CANDIDATE_TRACE_FORMAT_VERSION,
435 configured_input,
436 configured_input_digest,
437 execution_policy,
438 execution_policy_digest,
439 execution_policy_complete,
440 input_provenance,
441 input_provenance_digest,
442 qualified_run_provenance: None,
443 resolved_phase_plan,
444 resolved_phase_plan_digest,
445 resolved_phase_plan_complete,
446 }
447 }
448
449 pub fn new_qualified(
450 configured_input: String,
451 execution_policy: CandidateTraceExecutionPolicy,
452 resolved_phase_plan: CandidateTracePhasePlan,
453 qualified_run_provenance: QualifiedCandidateTraceRunProvenance,
454 ) -> Self {
455 let mut header = Self::new(
456 configured_input,
457 execution_policy,
458 resolved_phase_plan,
459 Some(qualified_run_provenance.input_provenance().clone()),
460 );
461 header.qualified_run_provenance = Some(qualified_run_provenance);
462 header
463 }
464}
465
466#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
468pub enum CandidateTraceSource {
469 Construction,
470 LocalSearch,
471 VariableNeighborhoodDescent,
472 KOpt,
473 ListRoundRobinConstruction,
474 ListCheapestInsertionTrial,
475 ListRegretInsertionTrial,
476 ListClarkeWrightSavings,
477 ListClarkeWrightMerge,
478 ListClarkeWrightCompletionInsertion,
479 ListKOptReconnection,
480 ListRegretOwnerAppend,
481}
482
483impl CandidateTraceSource {
484 const fn code(self) -> u8 {
485 match self {
486 Self::Construction => 1,
487 Self::LocalSearch => 2,
488 Self::VariableNeighborhoodDescent => 3,
489 Self::KOpt => 4,
490 Self::ListRoundRobinConstruction => 5,
491 Self::ListCheapestInsertionTrial => 6,
492 Self::ListRegretInsertionTrial => 7,
493 Self::ListClarkeWrightSavings => 8,
494 Self::ListClarkeWrightMerge => 9,
495 Self::ListClarkeWrightCompletionInsertion => 10,
496 Self::ListKOptReconnection => 11,
497 Self::ListRegretOwnerAppend => 12,
498 }
499 }
500}
501
502#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
508pub enum CandidateTraceDisposition {
509 InterruptedBeforeEvaluation,
512 Evaluated,
514 NotDoable,
516 RejectedByHardImprovement,
518 RejectedByScoreImprovement,
520 AcceptorRejected,
522 ForagerIgnored,
524 Selected,
526 Applied,
528 DiscardedSpeculative,
531}
532
533impl CandidateTraceDisposition {
534 const fn code(self) -> u8 {
535 match self {
536 Self::InterruptedBeforeEvaluation => 1,
537 Self::Evaluated => 2,
538 Self::NotDoable => 3,
539 Self::RejectedByHardImprovement => 4,
540 Self::RejectedByScoreImprovement => 5,
541 Self::AcceptorRejected => 6,
542 Self::ForagerIgnored => 7,
543 Self::Selected => 8,
544 Self::Applied => 9,
545 Self::DiscardedSpeculative => 10,
546 }
547 }
548
549 const fn is_terminal(self) -> bool {
550 matches!(
551 self,
552 Self::InterruptedBeforeEvaluation
553 | Self::NotDoable
554 | Self::RejectedByHardImprovement
555 | Self::RejectedByScoreImprovement
556 | Self::AcceptorRejected
557 | Self::ForagerIgnored
558 | Self::Applied
559 | Self::DiscardedSpeculative
560 )
561 }
562}
563
564#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
568pub(crate) struct CandidateTracePullToken {
569 ordinal: u64,
570}
571
572#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
575pub struct CandidateTraceConstructionTarget {
576 pub descriptor_index: usize,
577 pub entity_index: usize,
578}
579
580#[derive(Debug, Clone, PartialEq, Eq)]
590pub enum CandidateTraceIdentity {
591 Operation(CandidateTraceOperationIdentity),
594 Composite(CandidateTraceCompositeIdentity),
597}
598
599#[derive(Debug, Clone, PartialEq, Eq)]
605pub struct CandidateTraceOperationIdentity {
606 pub descriptor_index: usize,
607 pub variable_name: Option<String>,
610 pub operation: String,
613 pub components: Vec<CandidateTraceCoordinate>,
614}
615
616#[derive(Debug, Clone, PartialEq, Eq)]
622pub struct CandidateTraceCompositeIdentity {
623 pub operation: String,
624 pub children: Vec<CandidateTraceIdentity>,
625}
626
627#[derive(Debug, Clone, PartialEq, Eq, Hash)]
633pub enum CandidateTraceCoordinate {
634 Unsigned(u64),
635 Absent,
636 Text(String),
639 Bytes(Vec<u8>),
643}
644
645impl From<u64> for CandidateTraceCoordinate {
646 fn from(value: u64) -> Self {
647 Self::Unsigned(value)
648 }
649}
650
651impl From<usize> for CandidateTraceCoordinate {
652 fn from(value: usize) -> Self {
653 Self::Unsigned(value as u64)
654 }
655}
656
657impl From<Option<usize>> for CandidateTraceCoordinate {
658 fn from(value: Option<usize>) -> Self {
659 value.map_or(Self::Absent, Self::from)
660 }
661}
662
663impl From<String> for CandidateTraceCoordinate {
664 fn from(value: String) -> Self {
665 Self::Text(value)
666 }
667}
668
669impl From<&str> for CandidateTraceCoordinate {
670 fn from(value: &str) -> Self {
671 Self::Text(value.to_owned())
672 }
673}
674
675impl CandidateTraceIdentity {
676 pub fn operation<I, T>(
677 descriptor_index: usize,
678 operation: impl Into<String>,
679 components: I,
680 ) -> Self
681 where
682 I: IntoIterator<Item = T>,
683 T: Into<CandidateTraceCoordinate>,
684 {
685 Self::Operation(CandidateTraceOperationIdentity {
686 descriptor_index,
687 variable_name: None,
688 operation: operation.into(),
689 components: components.into_iter().map(Into::into).collect(),
690 })
691 }
692
693 pub fn logical_move<I, T>(
694 descriptor_index: usize,
695 variable_name: impl Into<String>,
696 family: impl Into<String>,
697 coordinates: I,
698 ) -> Self
699 where
700 I: IntoIterator<Item = T>,
701 T: Into<CandidateTraceCoordinate>,
702 {
703 Self::Operation(CandidateTraceOperationIdentity {
704 descriptor_index,
705 variable_name: Some(variable_name.into()),
706 operation: family.into(),
707 components: coordinates.into_iter().map(Into::into).collect(),
708 })
709 }
710
711 pub fn composite(
714 operation: impl Into<String>,
715 children: impl IntoIterator<Item = CandidateTraceIdentity>,
716 ) -> Self {
717 Self::Composite(CandidateTraceCompositeIdentity {
718 operation: operation.into(),
719 children: children.into_iter().collect(),
720 })
721 }
722
723 pub fn canonical_bytes(&self) -> Vec<u8> {
724 let mut out = Vec::new();
725 self.append_canonical_bytes(&mut out);
726 out
727 }
728
729 fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
730 match self {
731 Self::Operation(identity) => {
732 out.push(0x4f);
733 append_usize(out, identity.descriptor_index);
734 match &identity.variable_name {
735 Some(variable_name) => {
736 append_bool(out, true);
737 append_string(out, variable_name);
738 }
739 None => append_bool(out, false),
740 }
741 append_string(out, &identity.operation);
742 append_coordinate_list(out, &identity.components);
743 }
744 Self::Composite(identity) => {
745 out.push(0x43);
746 append_string(out, &identity.operation);
747 append_len(out, identity.children.len());
748 for child in &identity.children {
749 child.append_canonical_bytes(out);
750 }
751 }
752 }
753 }
754}
755
756#[derive(Debug, Clone, PartialEq, Eq)]
758pub struct CandidatePullTelemetry {
759 pub ordinal: u64,
761 pub source: CandidateTraceSource,
762 pub phase_index: usize,
763 pub phase_type: String,
764 pub step_index: u64,
765 pub selector_index: Option<usize>,
766 pub candidate_index: usize,
773 pub construction_target: Option<CandidateTraceConstructionTarget>,
774 pub identity: Option<CandidateTraceIdentity>,
777 pub dispositions: Vec<CandidateTraceDisposition>,
780}
781
782impl CandidatePullTelemetry {
783 fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
784 out.push(0x45);
785 append_u64(out, self.ordinal);
786 out.push(self.source.code());
787 append_usize(out, self.phase_index);
788 append_string(out, &self.phase_type);
789 append_u64(out, self.step_index);
790 append_option_usize(out, self.selector_index);
791 append_usize(out, self.candidate_index);
792 match self.construction_target {
793 Some(target) => {
794 append_bool(out, true);
795 append_usize(out, target.descriptor_index);
796 append_usize(out, target.entity_index);
797 }
798 None => append_bool(out, false),
799 }
800 match &self.identity {
801 Some(identity) => {
802 append_bool(out, true);
803 identity.append_canonical_bytes(out);
804 }
805 None => append_bool(out, false),
806 }
807 append_len(out, self.dispositions.len());
808 for disposition in &self.dispositions {
809 out.push(disposition.code());
810 }
811 }
812
813 fn has_terminal_disposition(&self) -> bool {
814 self.dispositions
815 .last()
816 .is_some_and(|disposition| disposition.is_terminal())
817 }
818}
819
820#[derive(Debug, Clone, PartialEq, Eq)]
822pub struct CandidateTraceTelemetry {
823 pub header: CandidateTraceHeader,
824 pub max_entries: usize,
825 pub total_pulls: u64,
827 pub pulls: Vec<CandidatePullTelemetry>,
829 pub truncated: bool,
831 pub prefix_digest: CandidateTraceDigest,
833 pub unencoded_identity_count: u64,
836 resolved_phase_plan_finalized: bool,
840 next_phase_index: usize,
841}
842
843impl CandidateTraceTelemetry {
844 pub(crate) fn new(header: CandidateTraceHeader, max_entries: usize) -> Self {
845 Self {
846 header,
847 max_entries,
848 total_pulls: 0,
849 pulls: Vec::new(),
853 truncated: false,
854 prefix_digest: CandidateTraceDigest::empty(),
855 unencoded_identity_count: 0,
856 resolved_phase_plan_finalized: false,
857 next_phase_index: 0,
858 }
859 }
860
861 pub(crate) fn finalize_resolved_phase_plan(
869 &mut self,
870 resolved_phase_plan: CandidateTracePhasePlan,
871 ) {
872 assert!(
873 !self.resolved_phase_plan_finalized,
874 "candidate trace resolved phase plan may be finalized only once"
875 );
876 let resolved_phase_plan_digest =
877 CandidateTraceDigest::of_bytes(&resolved_phase_plan.canonical_bytes());
878 let resolved_phase_plan_complete = resolved_phase_plan.is_complete();
879 self.header.resolved_phase_plan = resolved_phase_plan;
880 self.header.resolved_phase_plan_digest = resolved_phase_plan_digest;
881 self.header.resolved_phase_plan_complete = resolved_phase_plan_complete;
882 self.resolved_phase_plan_finalized = true;
883 }
884
885 pub fn is_complete(&self) -> bool {
886 !self.truncated
887 && self.total_pulls == self.pulls.len() as u64
888 && self.unencoded_identity_count == 0
889 && self
890 .pulls
891 .iter()
892 .all(CandidatePullTelemetry::has_terminal_disposition)
893 }
894
895 pub fn has_complete_execution_provenance(&self) -> bool {
902 self.header.execution_policy_complete && self.header.resolved_phase_plan_complete
903 }
904
905 pub fn provenance_status(&self) -> CandidateTraceProvenanceStatus {
906 let input_provenance = match self.header.input_provenance.as_ref() {
907 None => CandidateTraceInputProvenanceStatus::Absent,
908 Some(_) => CandidateTraceInputProvenanceStatus::ExternallyAttested,
909 };
910 CandidateTraceProvenanceStatus {
911 execution_policy_complete: self.header.execution_policy_complete,
912 resolved_phase_plan_complete: self.header.resolved_phase_plan_complete,
913 input_provenance,
914 qualification: self
915 .header
916 .qualified_run_provenance
917 .as_ref()
918 .map_or(CandidateTraceQualificationStatus::NotRequested, |_| {
919 CandidateTraceQualificationStatus::Qualified
920 }),
921 }
922 }
923
924 pub fn require_qualified_run_provenance(
928 &self,
929 ) -> Result<&QualifiedCandidateTraceRunProvenance, CandidateTraceQualificationError> {
930 self.header
931 .qualified_run_provenance
932 .as_ref()
933 .ok_or(CandidateTraceQualificationError::QualificationNotRequested)
934 }
935
936 pub(crate) fn prepare_pull(&mut self) -> CandidateTraceRecordDecision {
937 let ordinal = self.total_pulls;
938 self.total_pulls = self.total_pulls.saturating_add(1);
939 if self.pulls.len() < self.max_entries {
940 CandidateTraceRecordDecision::Capture { ordinal }
941 } else {
942 self.truncated = true;
943 CandidateTraceRecordDecision::Overflow
944 }
945 }
946
947 pub(crate) fn begin_phase(&mut self) -> usize {
948 let phase_index = self.next_phase_index;
949 self.next_phase_index = self.next_phase_index.saturating_add(1);
950 phase_index
951 }
952
953 pub(crate) fn push_prepared(
954 &mut self,
955 pull: CandidatePullTelemetry,
956 ) -> CandidateTracePullToken {
957 debug_assert!(self.pulls.len() < self.max_entries);
958 debug_assert_eq!(pull.ordinal, self.total_pulls.saturating_sub(1));
959 if pull.identity.is_none() {
960 self.unencoded_identity_count = self.unencoded_identity_count.saturating_add(1);
961 }
962 let token = CandidateTracePullToken {
963 ordinal: pull.ordinal,
964 };
965 self.pulls.push(pull);
966 token
967 }
968
969 pub(crate) fn record_disposition(
970 &mut self,
971 token: CandidateTracePullToken,
972 disposition: CandidateTraceDisposition,
973 ) {
974 let Some(pull) = self.pulls.get_mut(token.ordinal as usize) else {
975 debug_assert!(false, "candidate trace token must point at a retained pull");
976 return;
977 };
978 debug_assert_eq!(pull.ordinal, token.ordinal);
979 pull.dispositions.push(disposition);
980 }
981
982 pub(crate) fn snapshot(&self) -> Self {
987 let mut snapshot = self.clone();
988 snapshot.prefix_digest = CandidateTraceDigest::empty();
989 for pull in &snapshot.pulls {
990 let mut bytes = Vec::new();
991 pull.append_canonical_bytes(&mut bytes);
992 snapshot.prefix_digest.update(&bytes);
993 }
994 snapshot
995 }
996}
997
998#[derive(Debug, Clone, Copy, PartialEq, Eq)]
999pub(crate) enum CandidateTraceRecordDecision {
1000 Disabled,
1001 Capture { ordinal: u64 },
1002 Overflow,
1003}
1004
1005fn append_bool(out: &mut Vec<u8>, value: bool) {
1006 out.push(u8::from(value));
1007}
1008
1009fn append_len(out: &mut Vec<u8>, value: usize) {
1010 append_u64(
1011 out,
1012 u64::try_from(value).expect("candidate trace lengths must fit in u64"),
1013 );
1014}
1015
1016fn append_usize(out: &mut Vec<u8>, value: usize) {
1017 append_len(out, value);
1018}
1019
1020fn append_u64(out: &mut Vec<u8>, value: u64) {
1021 out.extend_from_slice(&value.to_le_bytes());
1022}
1023
1024fn append_option_usize(out: &mut Vec<u8>, value: Option<usize>) {
1025 match value {
1026 Some(value) => {
1027 append_bool(out, true);
1028 append_usize(out, value);
1029 }
1030 None => append_bool(out, false),
1031 }
1032}
1033
1034fn append_optional_external_digest(out: &mut Vec<u8>, value: Option<CandidateTraceExternalDigest>) {
1035 match value {
1036 Some(value) => {
1037 append_bool(out, true);
1038 value.append_canonical_bytes(out);
1039 }
1040 None => append_bool(out, false),
1041 }
1042}
1043
1044fn append_string(out: &mut Vec<u8>, value: &str) {
1045 append_len(out, value.len());
1046 out.extend_from_slice(value.as_bytes());
1047}
1048
1049fn append_coordinate_list(out: &mut Vec<u8>, values: &[CandidateTraceCoordinate]) {
1050 append_len(out, values.len());
1051 for value in values {
1052 match value {
1053 CandidateTraceCoordinate::Unsigned(value) => {
1054 out.push(1);
1055 append_u64(out, *value);
1056 }
1057 CandidateTraceCoordinate::Absent => out.push(2),
1058 CandidateTraceCoordinate::Text(value) => {
1059 out.push(3);
1060 append_string(out, value);
1061 }
1062 CandidateTraceCoordinate::Bytes(value) => {
1063 out.push(4);
1064 append_len(out, value.len());
1065 out.extend_from_slice(value);
1066 }
1067 }
1068 }
1069}