Skip to main content

harn_vm/vm/
execution_evidence.rs

1use super::Vm;
2
3impl Vm {
4    /// Enable exact, value-free source-path recording for this VM execution
5    /// tree. Child VMs inherit the same bounded recorder.
6    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    /// Durable identity of the current or most recently completed execution.
25    pub fn execution_id(&self) -> &crate::ExecutionId {
26        &self.execution_id
27    }
28
29    /// Snapshot the current flight recording without disabling it.
30    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    /// Snapshot the canonical evidence envelope for this execution tree.
37    /// Hosts provide persistence outcomes; Harn owns identity, schema, spans,
38    /// and the relationship between those facts.
39    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}