Skip to main content

harn_vm/vm/
callable_entry.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::chunk::ChunkRef;
5use crate::value::{VmError, VmValue};
6
7use super::{ScopeSpan, Vm};
8
9pub(super) enum TopLevelEntry {
10    Chunk(ChunkRef),
11    Callable {
12        bootstrap: ChunkRef,
13        has_fixture: bool,
14        fixture_expects_harness: bool,
15        expects_harness: bool,
16        args: Vec<VmValue>,
17    },
18}
19
20impl TopLevelEntry {
21    pub(super) async fn run(self, vm: &mut Vm) -> Result<VmValue, VmError> {
22        match self {
23            Self::Chunk(chunk) => vm.run_chunk(chunk).await,
24            Self::Callable {
25                bootstrap,
26                has_fixture,
27                fixture_expects_harness,
28                expects_harness,
29                mut args,
30            } => {
31                let value = vm.run_chunk(bootstrap).await?;
32                let target = if has_fixture {
33                    let VmValue::List(callables) = value else {
34                        return Err(VmError::Runtime(
35                            "callable entry bootstrap did not return [fixture, target]".to_string(),
36                        ));
37                    };
38                    let [VmValue::Closure(fixture), VmValue::Closure(target)] =
39                        callables.as_slice()
40                    else {
41                        return Err(VmError::Runtime(
42                            "callable entry bootstrap returned invalid fixture callables"
43                                .to_string(),
44                        ));
45                    };
46                    let fixture_args = if fixture_expects_harness {
47                        vec![vm.root_harness_value().ok_or_else(|| {
48                            VmError::Runtime(
49                                "test fixture requires Harness, but no root Harness is installed"
50                                    .to_string(),
51                            )
52                        })?]
53                    } else {
54                        Vec::new()
55                    };
56                    let fixture_value = vm.call_closure_pub(fixture, &fixture_args).await?;
57                    args.insert(0, fixture_value);
58                    Arc::clone(target)
59                } else {
60                    let VmValue::Closure(target) = value else {
61                        return Err(VmError::Runtime(
62                            "callable entry bootstrap did not return a callable".to_string(),
63                        ));
64                    };
65                    target
66                };
67                if expects_harness {
68                    let harness = vm.root_harness_value().ok_or_else(|| {
69                        VmError::Runtime(
70                            "callable entry requires Harness, but no root Harness is installed"
71                                .to_string(),
72                        )
73                    })?;
74                    args.insert(0, harness);
75                }
76                vm.call_closure_pub(&target, &args).await
77            }
78        }
79    }
80}
81
82impl Vm {
83    /// Execute a compiled callable entry with explicit values under a host
84    /// wall-clock limit.
85    ///
86    /// The entry bootstrap initializes top-level state once. A bundled fixture
87    /// is then invoked with no arguments and its result is prepended to `args`;
88    /// the target callable is invoked through ordinary arity/type guards.
89    /// Pipeline-finish hooks wrap the complete operation exactly once.
90    pub async fn execute_callable_entry_with_timeout(
91        &mut self,
92        entry: &crate::CompiledCallableEntry,
93        args: &[VmValue],
94        timeout: Duration,
95    ) -> Result<VmValue, VmError> {
96        self.execute_top_level_with_timeout(
97            TopLevelEntry::Callable {
98                bootstrap: Arc::new(entry.bootstrap.clone()),
99                has_fixture: entry.has_fixture,
100                fixture_expects_harness: entry.fixture_expects_harness,
101                expects_harness: entry.expects_harness,
102                args: args.to_vec(),
103            },
104            timeout,
105        )
106        .await
107    }
108
109    pub(super) async fn execute_top_level(
110        &mut self,
111        entry: TopLevelEntry,
112    ) -> Result<VmValue, VmError> {
113        self.ensure_execution_available()?;
114        let registry = self.pool_registry.clone();
115        let owner = crate::observability::execution_scope::mint_execution_scope();
116        let ambient = crate::orchestration::AmbientExecutionScope::capture_for_top_level_execution(
117            owner,
118            self.llm_mock_context.clone(),
119        );
120        let execution = crate::stdlib::pool::with_pool_registry_scope(registry, async {
121            self.execute_entry_scoped(entry).await
122        });
123        crate::orchestration::scope_ambient(ambient, execution).await
124    }
125
126    async fn execute_entry_scoped(&mut self, entry: TopLevelEntry) -> Result<VmValue, VmError> {
127        let _execution_activity = self
128            .wait_for_graph
129            .register_task(self.runtime_context.task_id.clone());
130        let _span = ScopeSpan::new(crate::tracing::SpanKind::Pipeline, "main".into());
131        match entry.run(self).await {
132            Ok(value) => self.run_pipeline_finish_lifecycle(value).await,
133            Err(error) => {
134                crate::orchestration::clear_pipeline_on_finish();
135                Err(error)
136            }
137        }
138    }
139}