harn_vm/vm/
execution_evidence.rs1use super::Vm;
2
3impl Vm {
4 pub fn enable_flight_recorder(&mut self, max_events: usize) {
7 self.flight_recorder_max_events = Some(max_events.max(1));
8 self.flight_recorder = Some(crate::flight_recorder::FlightRecorder::new(
9 self.execution_id.clone(),
10 max_events,
11 ));
12 }
13
14 pub(crate) fn prepare_execution_for_top_level(&mut self) {
15 if !self.owns_execution {
16 return;
17 }
18 self.execution_id = crate::observability::execution_scope::mint_execution_scope();
19 self.flight_recorder = self.flight_recorder_max_events.map(|max_events| {
20 crate::flight_recorder::FlightRecorder::new(self.execution_id.clone(), max_events)
21 });
22 }
23
24 pub fn execution_id(&self) -> &crate::ExecutionId {
26 &self.execution_id
27 }
28
29 pub fn flight_recording(&self) -> Option<crate::flight_recorder::FlightRecording> {
31 self.flight_recorder
32 .as_ref()
33 .map(|recorder| recorder.snapshot())
34 }
35
36 pub fn execution_evidence(
40 &self,
41 flight_recording: Option<crate::flight_recorder::FlightRecordingArtifact>,
42 gaps: Vec<crate::orchestration::RunEvidenceGapRecord>,
43 ) -> crate::orchestration::ExecutionEvidenceRecord {
44 crate::orchestration::ExecutionEvidenceRecord {
45 schema_version: crate::orchestration::EXECUTION_EVIDENCE_SCHEMA_VERSION,
46 execution_id: Some(self.execution_id.to_string()),
47 trace_spans: self
48 .tracing_runtime
49 .completed_spans_for_execution(&self.execution_id)
50 .iter()
51 .map(crate::orchestration::RunTraceSpanRecord::from)
52 .collect(),
53 flight_recording,
54 gaps,
55 }
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 struct TracingRuntimeGuard(std::sync::Arc<crate::tracing::TracingRuntime>);
62
63 impl Drop for TracingRuntimeGuard {
64 fn drop(&mut self) {
65 crate::tracing::swap_active_tracing_runtime(self.0.clone());
66 }
67 }
68
69 fn enter_tracing_runtime(
70 runtime: std::sync::Arc<crate::tracing::TracingRuntime>,
71 ) -> TracingRuntimeGuard {
72 TracingRuntimeGuard(crate::tracing::swap_active_tracing_runtime(runtime))
73 }
74
75 #[test]
76 fn vm_owns_the_execution_evidence_envelope() {
77 let vm = super::Vm::new();
78 let gap = crate::orchestration::RunEvidenceGapRecord {
79 component: "test".to_string(),
80 code: "named_gap".to_string(),
81 message: "named limit".to_string(),
82 };
83
84 let evidence = vm.execution_evidence(None, vec![gap.clone()]);
85
86 assert_eq!(
87 evidence.execution_id.as_deref(),
88 Some(vm.execution_id().as_str())
89 );
90 assert_eq!(
91 evidence.schema_version,
92 crate::orchestration::EXECUTION_EVIDENCE_SCHEMA_VERSION
93 );
94 assert_eq!(evidence.gaps, vec![gap]);
95 }
96
97 #[test]
98 fn vm_evidence_cannot_capture_the_callers_tracing_runtime() {
99 let mut vm = super::Vm::new();
100 vm.tracing_runtime = crate::tracing::fresh_tracing_runtime();
101 {
102 let _execution = crate::enter_execution_scope(vm.execution_id().clone());
103 let _runtime = enter_tracing_runtime(vm.tracing_runtime.clone());
104 crate::tracing::set_tracing_enabled(true);
105 let span =
106 crate::tracing::span_start(crate::tracing::SpanKind::Pipeline, "owned".to_string());
107 crate::tracing::span_end(span);
108 }
109
110 let caller_runtime = crate::tracing::fresh_tracing_runtime();
111 let caller_execution = crate::ExecutionId::mint();
112 let _execution = crate::enter_execution_scope(caller_execution);
113 let _caller = enter_tracing_runtime(caller_runtime);
114 crate::tracing::set_tracing_enabled(true);
115 let span =
116 crate::tracing::span_start(crate::tracing::SpanKind::Pipeline, "caller".to_string());
117 crate::tracing::span_end(span);
118
119 let evidence = vm.execution_evidence(None, Vec::new());
120 assert_eq!(
121 evidence
122 .trace_spans
123 .iter()
124 .map(|span| span.name.as_str())
125 .collect::<Vec<_>>(),
126 vec!["owned"]
127 );
128 }
129
130 #[test]
131 fn vm_evidence_selects_its_execution_from_a_reused_tracing_runtime() {
132 let mut vm = super::Vm::new();
133 vm.tracing_runtime = crate::tracing::fresh_tracing_runtime();
134 let _runtime = enter_tracing_runtime(vm.tracing_runtime.clone());
135 crate::tracing::set_tracing_enabled(true);
136
137 let earlier_execution = crate::ExecutionId::mint();
138 {
139 let _execution = crate::enter_execution_scope(earlier_execution);
140 let span = crate::tracing::span_start(
141 crate::tracing::SpanKind::Pipeline,
142 "earlier".to_string(),
143 );
144 crate::tracing::span_set_metadata(
145 span,
146 crate::tracing::meta::EXECUTION_ID,
147 serde_json::json!(vm.execution_id()),
148 );
149 crate::tracing::span_end(span);
150 }
151 {
152 let _execution = crate::enter_execution_scope(vm.execution_id().clone());
153 let span = crate::tracing::span_start(
154 crate::tracing::SpanKind::Pipeline,
155 "current".to_string(),
156 );
157 crate::tracing::span_set_metadata(
158 span,
159 crate::tracing::meta::EXECUTION_ID,
160 serde_json::json!("hxe-019c13e0-8080-7000-8000-000000000099"),
161 );
162 crate::tracing::span_end(span);
163 }
164
165 let evidence = vm.execution_evidence(None, Vec::new());
166 assert_eq!(
167 evidence
168 .trace_spans
169 .iter()
170 .map(|span| span.name.as_str())
171 .collect::<Vec<_>>(),
172 vec!["current"]
173 );
174 assert_eq!(
175 evidence.trace_spans[0].metadata[crate::tracing::meta::EXECUTION_ID],
176 serde_json::json!(vm.execution_id())
177 );
178 }
179}