Skip to main content

harn_vm/observability/
execution_scope.rs

1//! Ambient EXECUTION SCOPE — an immutable owner token minted once per Harn
2//! program run and readable, like [`crate::observability::request_id`], from
3//! hostlib builtins on the synchronous dispatch stack.
4//!
5//! Unlike `request_id` (which a *host* pushes only at served ingress and which
6//! is `None` for standalone `harn run`/`harn test`), the execution scope is
7//! established by the VM itself at the top-level program boundary
8//! (`Vm::execute_scoped`), so it is ALWAYS present during real execution and is
9//! never a shared sentinel. That is exactly what lets the verdict issuance
10//! authority bind a proof-of-execution receipt to the specific run that
11//! PRODUCED the evidence: `run_test` captures the active scope when it records a
12//! real execution, and `harness.verdict.issue` mints a positive verdict only
13//! when the current active scope EQUALS that captured owner — so an old green
14//! handle cannot bless a later, different run (the cross-run replay class).
15//!
16//! The scope is a stack so nested program executions restore the outer owner on
17//! return; the innermost entry wins for [`current_execution_scope`]. When no
18//! scope is active (there is no owning execution), issuance FAILS CLOSED — it
19//! never falls back to a default owner.
20
21use std::cell::RefCell;
22use std::sync::Arc;
23
24/// Stable prefix for VM-owned execution identities.
25pub const EXECUTION_ID_PREFIX: &str = "hxe-";
26
27thread_local! {
28    static ACTIVE_EXECUTION_SCOPE_STACK: RefCell<Vec<Arc<str>>> = const { RefCell::new(Vec::new()) };
29}
30
31/// Mint a fresh, durable execution id. UUIDv7 keeps identifiers unique across
32/// processes and hosts while preserving useful creation-time ordering.
33pub fn mint_execution_scope() -> Arc<str> {
34    Arc::from(format!("{EXECUTION_ID_PREFIX}{}", uuid::Uuid::now_v7()))
35}
36
37/// RAII guard returned by [`enter_execution_scope`]. Popping the stack on drop
38/// keeps the ambient balanced even when the enclosed program run panics or
39/// returns an error.
40#[must_use = "dropping the guard immediately pops the execution scope"]
41pub struct ExecutionScopeGuard {
42    _private: (),
43}
44
45impl Drop for ExecutionScopeGuard {
46    fn drop(&mut self) {
47        ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| {
48            stack.borrow_mut().pop();
49        });
50    }
51}
52
53/// Push `scope` onto the ambient stack for the lifetime of the returned guard.
54/// The innermost entry wins for [`current_execution_scope`].
55pub fn enter_execution_scope(scope: Arc<str>) -> ExecutionScopeGuard {
56    ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| stack.borrow_mut().push(scope));
57    ExecutionScopeGuard { _private: () }
58}
59
60/// Currently-active execution scope, or `None` when no owning program run is
61/// active on this task. Verdict issuance treats `None` as fail-closed.
62pub fn current_execution_scope() -> Option<Arc<str>> {
63    ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| stack.borrow().last().cloned())
64}
65
66/// Replace the whole ambient stack, returning the previous one. Used by the
67/// orchestration ambient-scope machinery to carry the owner across
68/// `spawn_local` fan-out boundaries (which plain thread-locals do not cross),
69/// mirroring how the current-session stack is propagated.
70pub(crate) fn swap_execution_scope_stack(replacement: Vec<Arc<str>>) -> Vec<Arc<str>> {
71    ACTIVE_EXECUTION_SCOPE_STACK
72        .with(|stack| std::mem::replace(&mut *stack.borrow_mut(), replacement))
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn current_returns_none_when_nothing_pushed() {
81        // A fresh thread has no owning execution.
82        std::thread::spawn(|| {
83            assert_eq!(current_execution_scope(), None);
84        })
85        .join()
86        .unwrap();
87    }
88
89    #[test]
90    fn guard_pops_on_drop_and_inner_shadows_outer() {
91        std::thread::spawn(|| {
92            let outer = mint_execution_scope();
93            let inner = mint_execution_scope();
94            assert_ne!(outer, inner);
95            let _o = enter_execution_scope(outer.clone());
96            assert_eq!(current_execution_scope().as_deref(), Some(&*outer));
97            {
98                let _i = enter_execution_scope(inner.clone());
99                assert_eq!(current_execution_scope().as_deref(), Some(&*inner));
100            }
101            assert_eq!(current_execution_scope().as_deref(), Some(&*outer));
102        })
103        .join()
104        .unwrap();
105    }
106}