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}
529
530impl CandidateTraceDisposition {
531 const fn code(self) -> u8 {
532 match self {
533 Self::InterruptedBeforeEvaluation => 1,
534 Self::Evaluated => 2,
535 Self::NotDoable => 3,
536 Self::RejectedByHardImprovement => 4,
537 Self::RejectedByScoreImprovement => 5,
538 Self::AcceptorRejected => 6,
539 Self::ForagerIgnored => 7,
540 Self::Selected => 8,
541 Self::Applied => 9,
542 }
543 }
544
545 const fn is_terminal(self) -> bool {
546 matches!(
547 self,
548 Self::InterruptedBeforeEvaluation
549 | Self::NotDoable
550 | Self::RejectedByHardImprovement
551 | Self::RejectedByScoreImprovement
552 | Self::AcceptorRejected
553 | Self::ForagerIgnored
554 | Self::Applied
555 )
556 }
557}
558
559#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
563pub(crate) struct CandidateTracePullToken {
564 ordinal: u64,
565}
566
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
570pub struct CandidateTraceConstructionTarget {
571 pub descriptor_index: usize,
572 pub entity_index: usize,
573}
574
575#[derive(Debug, Clone, PartialEq, Eq)]
585pub enum CandidateTraceIdentity {
586 Operation(CandidateTraceOperationIdentity),
589 Composite(CandidateTraceCompositeIdentity),
592}
593
594#[derive(Debug, Clone, PartialEq, Eq)]
600pub struct CandidateTraceOperationIdentity {
601 pub descriptor_index: usize,
602 pub variable_name: Option<String>,
605 pub operation: String,
608 pub components: Vec<CandidateTraceCoordinate>,
609}
610
611#[derive(Debug, Clone, PartialEq, Eq)]
617pub struct CandidateTraceCompositeIdentity {
618 pub operation: String,
619 pub children: Vec<CandidateTraceIdentity>,
620}
621
622#[derive(Debug, Clone, PartialEq, Eq, Hash)]
628pub enum CandidateTraceCoordinate {
629 Unsigned(u64),
630 Absent,
631 Text(String),
634 Bytes(Vec<u8>),
638}
639
640impl From<u64> for CandidateTraceCoordinate {
641 fn from(value: u64) -> Self {
642 Self::Unsigned(value)
643 }
644}
645
646impl From<usize> for CandidateTraceCoordinate {
647 fn from(value: usize) -> Self {
648 Self::Unsigned(value as u64)
649 }
650}
651
652impl From<Option<usize>> for CandidateTraceCoordinate {
653 fn from(value: Option<usize>) -> Self {
654 value.map_or(Self::Absent, Self::from)
655 }
656}
657
658impl From<String> for CandidateTraceCoordinate {
659 fn from(value: String) -> Self {
660 Self::Text(value)
661 }
662}
663
664impl From<&str> for CandidateTraceCoordinate {
665 fn from(value: &str) -> Self {
666 Self::Text(value.to_owned())
667 }
668}
669
670impl CandidateTraceIdentity {
671 pub fn operation<I, T>(
672 descriptor_index: usize,
673 operation: impl Into<String>,
674 components: I,
675 ) -> Self
676 where
677 I: IntoIterator<Item = T>,
678 T: Into<CandidateTraceCoordinate>,
679 {
680 Self::Operation(CandidateTraceOperationIdentity {
681 descriptor_index,
682 variable_name: None,
683 operation: operation.into(),
684 components: components.into_iter().map(Into::into).collect(),
685 })
686 }
687
688 pub fn logical_move<I, T>(
689 descriptor_index: usize,
690 variable_name: impl Into<String>,
691 family: impl Into<String>,
692 coordinates: I,
693 ) -> Self
694 where
695 I: IntoIterator<Item = T>,
696 T: Into<CandidateTraceCoordinate>,
697 {
698 Self::Operation(CandidateTraceOperationIdentity {
699 descriptor_index,
700 variable_name: Some(variable_name.into()),
701 operation: family.into(),
702 components: coordinates.into_iter().map(Into::into).collect(),
703 })
704 }
705
706 pub fn composite(
709 operation: impl Into<String>,
710 children: impl IntoIterator<Item = CandidateTraceIdentity>,
711 ) -> Self {
712 Self::Composite(CandidateTraceCompositeIdentity {
713 operation: operation.into(),
714 children: children.into_iter().collect(),
715 })
716 }
717
718 pub fn canonical_bytes(&self) -> Vec<u8> {
719 let mut out = Vec::new();
720 self.append_canonical_bytes(&mut out);
721 out
722 }
723
724 fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
725 match self {
726 Self::Operation(identity) => {
727 out.push(0x4f);
728 append_usize(out, identity.descriptor_index);
729 match &identity.variable_name {
730 Some(variable_name) => {
731 append_bool(out, true);
732 append_string(out, variable_name);
733 }
734 None => append_bool(out, false),
735 }
736 append_string(out, &identity.operation);
737 append_coordinate_list(out, &identity.components);
738 }
739 Self::Composite(identity) => {
740 out.push(0x43);
741 append_string(out, &identity.operation);
742 append_len(out, identity.children.len());
743 for child in &identity.children {
744 child.append_canonical_bytes(out);
745 }
746 }
747 }
748 }
749}
750
751#[derive(Debug, Clone, PartialEq, Eq)]
753pub struct CandidatePullTelemetry {
754 pub ordinal: u64,
756 pub source: CandidateTraceSource,
757 pub phase_index: usize,
758 pub phase_type: String,
759 pub step_index: u64,
760 pub selector_index: Option<usize>,
761 pub candidate_index: usize,
768 pub construction_target: Option<CandidateTraceConstructionTarget>,
769 pub identity: Option<CandidateTraceIdentity>,
772 pub dispositions: Vec<CandidateTraceDisposition>,
775}
776
777impl CandidatePullTelemetry {
778 fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
779 out.push(0x45);
780 append_u64(out, self.ordinal);
781 out.push(self.source.code());
782 append_usize(out, self.phase_index);
783 append_string(out, &self.phase_type);
784 append_u64(out, self.step_index);
785 append_option_usize(out, self.selector_index);
786 append_usize(out, self.candidate_index);
787 match self.construction_target {
788 Some(target) => {
789 append_bool(out, true);
790 append_usize(out, target.descriptor_index);
791 append_usize(out, target.entity_index);
792 }
793 None => append_bool(out, false),
794 }
795 match &self.identity {
796 Some(identity) => {
797 append_bool(out, true);
798 identity.append_canonical_bytes(out);
799 }
800 None => append_bool(out, false),
801 }
802 append_len(out, self.dispositions.len());
803 for disposition in &self.dispositions {
804 out.push(disposition.code());
805 }
806 }
807
808 fn has_terminal_disposition(&self) -> bool {
809 self.dispositions
810 .last()
811 .is_some_and(|disposition| disposition.is_terminal())
812 }
813}
814
815#[derive(Debug, Clone, PartialEq, Eq)]
817pub struct CandidateTraceTelemetry {
818 pub header: CandidateTraceHeader,
819 pub max_entries: usize,
820 pub total_pulls: u64,
822 pub pulls: Vec<CandidatePullTelemetry>,
824 pub truncated: bool,
826 pub prefix_digest: CandidateTraceDigest,
828 pub unencoded_identity_count: u64,
831 resolved_phase_plan_finalized: bool,
835 next_phase_index: usize,
836}
837
838impl CandidateTraceTelemetry {
839 pub(crate) fn new(header: CandidateTraceHeader, max_entries: usize) -> Self {
840 Self {
841 header,
842 max_entries,
843 total_pulls: 0,
844 pulls: Vec::new(),
848 truncated: false,
849 prefix_digest: CandidateTraceDigest::empty(),
850 unencoded_identity_count: 0,
851 resolved_phase_plan_finalized: false,
852 next_phase_index: 0,
853 }
854 }
855
856 pub(crate) fn finalize_resolved_phase_plan(
864 &mut self,
865 resolved_phase_plan: CandidateTracePhasePlan,
866 ) {
867 assert!(
868 !self.resolved_phase_plan_finalized,
869 "candidate trace resolved phase plan may be finalized only once"
870 );
871 let resolved_phase_plan_digest =
872 CandidateTraceDigest::of_bytes(&resolved_phase_plan.canonical_bytes());
873 let resolved_phase_plan_complete = resolved_phase_plan.is_complete();
874 self.header.resolved_phase_plan = resolved_phase_plan;
875 self.header.resolved_phase_plan_digest = resolved_phase_plan_digest;
876 self.header.resolved_phase_plan_complete = resolved_phase_plan_complete;
877 self.resolved_phase_plan_finalized = true;
878 }
879
880 pub fn is_complete(&self) -> bool {
881 !self.truncated
882 && self.total_pulls == self.pulls.len() as u64
883 && self.unencoded_identity_count == 0
884 && self
885 .pulls
886 .iter()
887 .all(CandidatePullTelemetry::has_terminal_disposition)
888 }
889
890 pub fn has_complete_execution_provenance(&self) -> bool {
897 self.header.execution_policy_complete && self.header.resolved_phase_plan_complete
898 }
899
900 pub fn provenance_status(&self) -> CandidateTraceProvenanceStatus {
901 let input_provenance = match self.header.input_provenance.as_ref() {
902 None => CandidateTraceInputProvenanceStatus::Absent,
903 Some(_) => CandidateTraceInputProvenanceStatus::ExternallyAttested,
904 };
905 CandidateTraceProvenanceStatus {
906 execution_policy_complete: self.header.execution_policy_complete,
907 resolved_phase_plan_complete: self.header.resolved_phase_plan_complete,
908 input_provenance,
909 qualification: self
910 .header
911 .qualified_run_provenance
912 .as_ref()
913 .map_or(CandidateTraceQualificationStatus::NotRequested, |_| {
914 CandidateTraceQualificationStatus::Qualified
915 }),
916 }
917 }
918
919 pub fn require_qualified_run_provenance(
923 &self,
924 ) -> Result<&QualifiedCandidateTraceRunProvenance, CandidateTraceQualificationError> {
925 self.header
926 .qualified_run_provenance
927 .as_ref()
928 .ok_or(CandidateTraceQualificationError::QualificationNotRequested)
929 }
930
931 pub(crate) fn prepare_pull(&mut self) -> CandidateTraceRecordDecision {
932 let ordinal = self.total_pulls;
933 self.total_pulls = self.total_pulls.saturating_add(1);
934 if self.pulls.len() < self.max_entries {
935 CandidateTraceRecordDecision::Capture { ordinal }
936 } else {
937 self.truncated = true;
938 CandidateTraceRecordDecision::Overflow
939 }
940 }
941
942 pub(crate) fn begin_phase(&mut self) -> usize {
943 let phase_index = self.next_phase_index;
944 self.next_phase_index = self.next_phase_index.saturating_add(1);
945 phase_index
946 }
947
948 pub(crate) fn push_prepared(
949 &mut self,
950 pull: CandidatePullTelemetry,
951 ) -> CandidateTracePullToken {
952 debug_assert!(self.pulls.len() < self.max_entries);
953 debug_assert_eq!(pull.ordinal, self.total_pulls.saturating_sub(1));
954 if pull.identity.is_none() {
955 self.unencoded_identity_count = self.unencoded_identity_count.saturating_add(1);
956 }
957 let token = CandidateTracePullToken {
958 ordinal: pull.ordinal,
959 };
960 self.pulls.push(pull);
961 token
962 }
963
964 pub(crate) fn record_disposition(
965 &mut self,
966 token: CandidateTracePullToken,
967 disposition: CandidateTraceDisposition,
968 ) {
969 let Some(pull) = self.pulls.get_mut(token.ordinal as usize) else {
970 debug_assert!(false, "candidate trace token must point at a retained pull");
971 return;
972 };
973 debug_assert_eq!(pull.ordinal, token.ordinal);
974 pull.dispositions.push(disposition);
975 }
976
977 pub(crate) fn snapshot(&self) -> Self {
982 let mut snapshot = self.clone();
983 snapshot.prefix_digest = CandidateTraceDigest::empty();
984 for pull in &snapshot.pulls {
985 let mut bytes = Vec::new();
986 pull.append_canonical_bytes(&mut bytes);
987 snapshot.prefix_digest.update(&bytes);
988 }
989 snapshot
990 }
991}
992
993#[derive(Debug, Clone, Copy, PartialEq, Eq)]
994pub(crate) enum CandidateTraceRecordDecision {
995 Disabled,
996 Capture { ordinal: u64 },
997 Overflow,
998}
999
1000fn append_bool(out: &mut Vec<u8>, value: bool) {
1001 out.push(u8::from(value));
1002}
1003
1004fn append_len(out: &mut Vec<u8>, value: usize) {
1005 append_u64(
1006 out,
1007 u64::try_from(value).expect("candidate trace lengths must fit in u64"),
1008 );
1009}
1010
1011fn append_usize(out: &mut Vec<u8>, value: usize) {
1012 append_len(out, value);
1013}
1014
1015fn append_u64(out: &mut Vec<u8>, value: u64) {
1016 out.extend_from_slice(&value.to_le_bytes());
1017}
1018
1019fn append_option_usize(out: &mut Vec<u8>, value: Option<usize>) {
1020 match value {
1021 Some(value) => {
1022 append_bool(out, true);
1023 append_usize(out, value);
1024 }
1025 None => append_bool(out, false),
1026 }
1027}
1028
1029fn append_optional_external_digest(out: &mut Vec<u8>, value: Option<CandidateTraceExternalDigest>) {
1030 match value {
1031 Some(value) => {
1032 append_bool(out, true);
1033 value.append_canonical_bytes(out);
1034 }
1035 None => append_bool(out, false),
1036 }
1037}
1038
1039fn append_string(out: &mut Vec<u8>, value: &str) {
1040 append_len(out, value.len());
1041 out.extend_from_slice(value.as_bytes());
1042}
1043
1044fn append_coordinate_list(out: &mut Vec<u8>, values: &[CandidateTraceCoordinate]) {
1045 append_len(out, values.len());
1046 for value in values {
1047 match value {
1048 CandidateTraceCoordinate::Unsigned(value) => {
1049 out.push(1);
1050 append_u64(out, *value);
1051 }
1052 CandidateTraceCoordinate::Absent => out.push(2),
1053 CandidateTraceCoordinate::Text(value) => {
1054 out.push(3);
1055 append_string(out, value);
1056 }
1057 CandidateTraceCoordinate::Bytes(value) => {
1058 out.push(4);
1059 append_len(out, value.len());
1060 out.extend_from_slice(value);
1061 }
1062 }
1063 }
1064}