Skip to main content

incurs_codemode/
codemode.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use incurs::command::RequestContext;
6use incurs::tool::{ToolCallControl, ToolEvent, ToolEventSink};
7use serde_json::Value;
8use tokio::sync::{Mutex, Semaphore};
9use tokio_util::sync::CancellationToken;
10
11use crate::{
12    ArtifactStore, CapabilitySnapshot, Clock, CodeExecutor, CodeModeRuntime, Connector,
13    DispatchRequest, DispatchSession, ExecutionEvent, ExecutionHost, ExecutionState,
14    ExecutionStatus, RuntimeStore, SearchOutput, SystemClock, ToolContext,
15};
16
17/// Transport context inherited by one Code Mode execution pass.
18#[derive(Clone, Default)]
19pub struct CodeModeRunOptions {
20    /// Cooperative cancellation signal for the active pass.
21    pub cancellation: CancellationToken,
22    /// Request metadata inherited by connector calls.
23    pub request: Option<RequestContext>,
24}
25
26struct RuntimeEventSink {
27    runtime: Arc<CodeModeRuntime>,
28    execution_id: String,
29    clock: Arc<dyn Clock>,
30}
31
32#[async_trait]
33impl ToolEventSink for RuntimeEventSink {
34    async fn emit(&self, event: ToolEvent) {
35        let at = self.clock.now_ms();
36        let event = match event {
37            ToolEvent::Progress { message, fraction } => ExecutionEvent::Progress {
38                message,
39                fraction,
40                at,
41            },
42            ToolEvent::Log { level, message } => ExecutionEvent::Log { level, message, at },
43            ToolEvent::Chunk { data } => ExecutionEvent::Chunk { data, at },
44        };
45        let _ = self.runtime.event(&self.execution_id, event).await;
46    }
47}
48
49/// Generic Code Mode lifecycle over interchangeable execution and storage adapters.
50pub struct CodeMode {
51    runtime: Arc<CodeModeRuntime>,
52    executor: Box<dyn CodeExecutor>,
53    connectors: Vec<Arc<dyn Connector>>,
54    clock: Arc<dyn Clock>,
55    contexts: Mutex<HashMap<String, ToolContext>>,
56    pass_gates: Mutex<HashMap<String, Arc<Semaphore>>>,
57    active_rollbacks: Mutex<HashSet<String>>,
58}
59
60impl CodeMode {
61    /// Creates a native Code Mode runtime using the system clock.
62    pub fn new(
63        store: Arc<dyn RuntimeStore>,
64        executor: impl CodeExecutor + 'static,
65        connectors: Vec<Arc<dyn Connector>>,
66    ) -> Self {
67        Self::with_clock(store, executor, connectors, SystemClock)
68    }
69
70    /// Creates Code Mode with an explicit oversized-value artifact store.
71    pub fn with_artifact_store(
72        store: Arc<dyn RuntimeStore>,
73        artifacts: Arc<dyn ArtifactStore>,
74        executor: impl CodeExecutor + 'static,
75        connectors: Vec<Arc<dyn Connector>>,
76    ) -> Self {
77        Self {
78            runtime: Arc::new(CodeModeRuntime::with_artifacts(store, artifacts)),
79            executor: Box::new(executor),
80            connectors,
81            clock: Arc::new(SystemClock),
82            contexts: Mutex::new(HashMap::new()),
83            pass_gates: Mutex::new(HashMap::new()),
84            active_rollbacks: Mutex::new(HashSet::new()),
85        }
86    }
87
88    /// Creates Code Mode with an explicit platform clock.
89    pub fn with_clock(
90        store: Arc<dyn RuntimeStore>,
91        executor: impl CodeExecutor + 'static,
92        connectors: Vec<Arc<dyn Connector>>,
93        clock: impl Clock + 'static,
94    ) -> Self {
95        Self {
96            runtime: Arc::new(CodeModeRuntime::new(store)),
97            executor: Box::new(executor),
98            connectors,
99            clock: Arc::new(clock),
100            contexts: Mutex::new(HashMap::new()),
101            pass_gates: Mutex::new(HashMap::new()),
102            active_rollbacks: Mutex::new(HashSet::new()),
103        }
104    }
105
106    /// Creates Code Mode with explicit platform clock and artifact storage.
107    pub fn with_clock_and_artifact_store(
108        store: Arc<dyn RuntimeStore>,
109        artifacts: Arc<dyn ArtifactStore>,
110        executor: impl CodeExecutor + 'static,
111        connectors: Vec<Arc<dyn Connector>>,
112        clock: impl Clock + 'static,
113    ) -> Self {
114        Self {
115            runtime: Arc::new(CodeModeRuntime::with_artifacts(store, artifacts)),
116            executor: Box::new(executor),
117            connectors,
118            clock: Arc::new(clock),
119            contexts: Mutex::new(HashMap::new()),
120            pass_gates: Mutex::new(HashMap::new()),
121            active_rollbacks: Mutex::new(HashSet::new()),
122        }
123    }
124
125    /// Returns the portable runtime for approvals, history, and snippets.
126    pub fn runtime(&self) -> Arc<CodeModeRuntime> {
127        Arc::clone(&self.runtime)
128    }
129
130    /// Returns model-facing JavaScript declarations and connector guidance.
131    pub async fn instructions(&self) -> Result<String, String> {
132        let mut descriptions = Vec::new();
133        for connector in &self.connectors {
134            descriptions.push(connector.describe().await?);
135        }
136        let mut sections = descriptions
137            .iter()
138            .filter_map(|connector| {
139                connector
140                    .instructions
141                    .as_ref()
142                    .map(|instructions| format!("## {}\n\n{instructions}", connector.name))
143            })
144            .collect::<Vec<_>>();
145        sections.extend(descriptions.iter().map(crate::generate_types));
146        Ok(sections.join("\n\n"))
147    }
148
149    /// Searches current connector methods and saved snippets.
150    pub async fn search(&self, query: &str) -> Result<SearchOutput, String> {
151        let mut descriptions = Vec::new();
152        for connector in &self.connectors {
153            descriptions.push(connector.describe().await?);
154        }
155        let snippets = self
156            .runtime
157            .snippets()
158            .await
159            .map_err(|error| error.to_string())?;
160        Ok(crate::search(query, &descriptions, &snippets))
161    }
162
163    /// Returns one execution by identifier.
164    pub async fn execution(&self, execution_id: &str) -> Result<ExecutionState, String> {
165        self.require(execution_id).await
166    }
167
168    /// Returns one bounded execution snapshot with artifact references intact.
169    pub async fn execution_snapshot(&self, execution_id: &str) -> Result<ExecutionState, String> {
170        self.runtime
171            .execution_snapshot(execution_id)
172            .await
173            .map_err(|error| error.to_string())?
174            .ok_or_else(|| format!("Execution \"{execution_id}\" not found"))
175    }
176
177    /// Returns one oversized value owned by an execution.
178    pub async fn artifact(&self, execution_id: &str, artifact_id: &str) -> Result<Value, String> {
179        self.runtime
180            .artifact(execution_id, artifact_id)
181            .await
182            .map_err(|error| error.to_string())?
183            .ok_or_else(|| {
184                format!("Artifact \"{artifact_id}\" not found for execution \"{execution_id}\"")
185            })
186    }
187
188    /// Returns ordered retained events for one execution.
189    pub async fn events(&self, execution_id: &str) -> Result<Vec<ExecutionEvent>, String> {
190        Ok(self.require(execution_id).await?.events)
191    }
192
193    /// Cancels a running or paused execution.
194    pub async fn cancel(&self, execution_id: &str) -> Result<ExecutionState, String> {
195        let pass_active = self.contexts.lock().await.contains_key(execution_id);
196        if let Some(context) = self.contexts.lock().await.get(execution_id) {
197            context.control.cancellation.cancel();
198        }
199        let changed = self
200            .runtime
201            .cancel(execution_id, self.clock.now_ms())
202            .await
203            .map_err(|error| error.to_string())?;
204        if changed && !pass_active {
205            self.notify_execution_end(execution_id, "cancelled").await;
206        }
207        self.require(execution_id).await
208    }
209
210    /// Creates a durable running execution without driving its first pass.
211    pub async fn start(&self, code: &str) -> Result<ExecutionState, String> {
212        let mut descriptions = Vec::new();
213        for connector in &self.connectors {
214            descriptions.push(connector.describe().await?);
215        }
216        let capabilities = CapabilitySnapshot::new(descriptions)?;
217        let id = self
218            .runtime
219            .begin_with_capabilities(code, capabilities, self.clock.now_ms())
220            .await
221            .map_err(|error| error.to_string())?;
222        self.require(&id).await
223    }
224
225    /// Starts and drives a new execution until it completes, fails, or pauses.
226    pub async fn execute(&self, code: &str) -> Result<ExecutionState, String> {
227        self.execute_with(code, CodeModeRunOptions::default()).await
228    }
229
230    /// Starts and drives a new execution with transport request context.
231    pub async fn execute_with(
232        &self,
233        code: &str,
234        options: CodeModeRunOptions,
235    ) -> Result<ExecutionState, String> {
236        let state = self.start(code).await?;
237        self.drive_with(&state.id, options).await
238    }
239
240    /// Resumes a paused execution and drives the next replay pass.
241    pub async fn resume(&self, execution_id: &str) -> Result<ExecutionState, String> {
242        self.resume_with(execution_id, CodeModeRunOptions::default())
243            .await
244    }
245
246    /// Resumes a paused execution with transport request context.
247    pub async fn resume_with(
248        &self,
249        execution_id: &str,
250        options: CodeModeRunOptions,
251    ) -> Result<ExecutionState, String> {
252        self.runtime
253            .resume(execution_id, self.clock.now_ms())
254            .await
255            .map_err(|error| error.to_string())?;
256        self.drive_with(execution_id, options).await
257    }
258
259    /// Approves a pending action and resumes only if this call won the race.
260    pub async fn approve(&self, execution_id: &str, seq: u64) -> Result<ExecutionState, String> {
261        self.approve_with(execution_id, seq, CodeModeRunOptions::default())
262            .await
263    }
264
265    /// Approves a pending action and resumes with transport request context.
266    pub async fn approve_with(
267        &self,
268        execution_id: &str,
269        seq: u64,
270        options: CodeModeRunOptions,
271    ) -> Result<ExecutionState, String> {
272        if !self
273            .runtime
274            .approve(execution_id, seq, self.clock.now_ms())
275            .await
276            .map_err(|error| error.to_string())?
277        {
278            return self.require(execution_id).await;
279        }
280        self.drive_with(execution_id, options).await
281    }
282
283    /// Rejects a pending action and notifies connector lifecycle hooks.
284    pub async fn reject(&self, execution_id: &str, seq: u64) -> Result<ExecutionState, String> {
285        if self
286            .runtime
287            .reject(execution_id, seq, self.clock.now_ms())
288            .await
289            .map_err(|error| error.to_string())?
290        {
291            self.notify_execution_end(execution_id, "rejected").await;
292        }
293        self.require(execution_id).await
294    }
295
296    /// Compensates applied connector actions in reverse order.
297    pub async fn rollback(&self, execution_id: &str) -> Result<ExecutionState, String> {
298        {
299            let mut active = self.active_rollbacks.lock().await;
300            if !active.insert(execution_id.to_string()) {
301                return Err(format!(
302                    "Execution \"{execution_id}\" is already rolling back"
303                ));
304            }
305        }
306        let result = self.rollback_inner(execution_id).await;
307        self.active_rollbacks.lock().await.remove(execution_id);
308        result
309    }
310
311    async fn rollback_inner(&self, execution_id: &str) -> Result<ExecutionState, String> {
312        for action in self
313            .runtime
314            .actions_to_revert(execution_id)
315            .await
316            .map_err(|error| error.to_string())?
317        {
318            let connector = self
319                .connector(&action.connector)
320                .await?
321                .ok_or_else(|| format!("Connector \"{}\" not found", action.connector))?;
322            if !connector
323                .revert(
324                    &action.method,
325                    action.arguments,
326                    action.result.unwrap_or(Value::Null),
327                    &ToolContext {
328                        execution_id: execution_id.to_string(),
329                        control: Default::default(),
330                        request: None,
331                    },
332                )
333                .await?
334            {
335                return Err(format!(
336                    "{}.{} did not compensate step {}",
337                    action.connector, action.method, action.seq
338                ));
339            }
340            self.runtime
341                .mark_reverted(execution_id, action.seq, self.clock.now_ms())
342                .await
343                .map_err(|error| error.to_string())?;
344        }
345        self.runtime
346            .finish_rollback(execution_id, self.clock.now_ms())
347            .await
348            .map_err(|error| error.to_string())?;
349        self.notify_execution_end(execution_id, "rolled_back").await;
350        self.require(execution_id).await
351    }
352
353    /// Expires stale live executions and releases connector resources.
354    pub async fn expire(&self, max_age_ms: u64) -> Result<Vec<String>, String> {
355        let ids = self
356            .runtime
357            .expire(self.clock.now_ms(), max_age_ms)
358            .await
359            .map_err(|error| error.to_string())?;
360        for id in &ids {
361            let status = self.require(id).await?.status;
362            self.notify_execution_end(
363                id,
364                if status == ExecutionStatus::Rejected {
365                    "rejected"
366                } else {
367                    "error"
368                },
369            )
370            .await;
371        }
372        Ok(ids)
373    }
374
375    /// Handles one program-to-host dispatch request.
376    pub async fn dispatch(&self, request: DispatchRequest) -> Result<Value, String> {
377        match request {
378            DispatchRequest::Call {
379                execution_id,
380                seq,
381                connector,
382                method,
383                arguments,
384            } => {
385                let session = self.session(&execution_id).await?;
386                serde_json::to_value(
387                    session
388                        .call_at(seq, &connector, &method, arguments, self.clock.now_ms())
389                        .await,
390                )
391                .map_err(|error| error.to_string())
392            }
393            DispatchRequest::BeginStep {
394                execution_id,
395                seq,
396                name,
397            } => {
398                let session = self.session(&execution_id).await?;
399                serde_json::to_value(
400                    session
401                        .begin_step_at(seq, &name, self.clock.now_ms())
402                        .await
403                        .map_err(|error| error.to_string())?,
404                )
405                .map_err(|error| error.to_string())
406            }
407            DispatchRequest::RecordStep {
408                execution_id,
409                seq,
410                result,
411            } => {
412                let _ = self.active_context(&execution_id).await?;
413                self.runtime
414                    .record_result(&execution_id, seq, result, self.clock.now_ms())
415                    .await
416                    .map_err(|error| error.to_string())?;
417                Ok(serde_json::json!({ "ok": true }))
418            }
419        }
420    }
421
422    /// Drives one running execution pass with transport request context.
423    pub async fn drive_with(
424        &self,
425        execution_id: &str,
426        options: CodeModeRunOptions,
427    ) -> Result<ExecutionState, String> {
428        let gate = {
429            let mut gates = self.pass_gates.lock().await;
430            Arc::clone(
431                gates
432                    .entry(execution_id.to_string())
433                    .or_insert_with(|| Arc::new(Semaphore::new(1))),
434            )
435        };
436        let _permit = gate
437            .acquire_owned()
438            .await
439            .map_err(|_| "Code Mode execution gate closed".to_string())?;
440        self.drive_pass(execution_id, options).await
441    }
442
443    async fn drive_pass(
444        &self,
445        execution_id: &str,
446        options: CodeModeRunOptions,
447    ) -> Result<ExecutionState, String> {
448        let state = self.require(execution_id).await?;
449        if matches!(
450            state.status,
451            ExecutionStatus::Completed
452                | ExecutionStatus::Error
453                | ExecutionStatus::Rejected
454                | ExecutionStatus::RolledBack
455                | ExecutionStatus::Cancelled
456        ) {
457            return Ok(state);
458        }
459        let context = self.context(execution_id, options);
460        let cancellation = context.control.cancellation.clone();
461        self.contexts
462            .lock()
463            .await
464            .insert(execution_id.to_string(), context.clone());
465        let session = match self.session_with_context(execution_id, context).await {
466            Ok(session) => session,
467            Err(error) => {
468                self.contexts.lock().await.remove(execution_id);
469                return Err(error);
470            }
471        };
472        let descriptions = session.descriptions();
473        let execution = self.executor.execute(
474            &state.code,
475            &descriptions,
476            execution_id,
477            Arc::new(ExecutionHost::new(
478                Arc::clone(&session),
479                Arc::clone(&self.clock),
480            )),
481        );
482        tokio::pin!(execution);
483        let response = tokio::select! {
484            _ = cancellation.cancelled() => None,
485            response = &mut execution => Some(response),
486        };
487        self.contexts.lock().await.remove(execution_id);
488        let Some(response) = response else {
489            self.runtime
490                .cancel(execution_id, self.clock.now_ms())
491                .await
492                .map_err(|error| error.to_string())?;
493            session.pass_ended("cancelled").await;
494            session.execution_ended("cancelled").await;
495            return self.require(execution_id).await;
496        };
497        let response = match response {
498            Ok(response) => response,
499            Err(error) => {
500                self.runtime
501                    .fail(execution_id, error, Vec::new(), self.clock.now_ms())
502                    .await
503                    .map_err(|error| error.to_string())?;
504                session.pass_ended("error").await;
505                session.execution_ended("error").await;
506                return self.require(execution_id).await;
507            }
508        };
509        let current = self.require(execution_id).await?;
510        if current.status == ExecutionStatus::Paused {
511            session.pass_ended("paused").await;
512            return Ok(current);
513        }
514        if current.status == ExecutionStatus::Error {
515            session.pass_ended("error").await;
516            session.execution_ended("error").await;
517            return Ok(current);
518        }
519        if current.status == ExecutionStatus::Cancelled {
520            session.pass_ended("cancelled").await;
521            session.execution_ended("cancelled").await;
522            return Ok(current);
523        }
524        if let Some(error) = response.error {
525            self.runtime
526                .fail(execution_id, error, response.logs, self.clock.now_ms())
527                .await
528                .map_err(|error| error.to_string())?;
529            session.pass_ended("error").await;
530            session.execution_ended("error").await;
531        } else {
532            self.runtime
533                .complete(
534                    execution_id,
535                    response.result.unwrap_or(Value::Null),
536                    response.logs,
537                    self.clock.now_ms(),
538                )
539                .await
540                .map_err(|error| error.to_string())?;
541            session.pass_ended("completed").await;
542            session.execution_ended("completed").await;
543        }
544        self.require(execution_id).await
545    }
546
547    async fn session(&self, execution_id: &str) -> Result<Arc<DispatchSession>, String> {
548        let context = self.active_context(execution_id).await?;
549        self.session_with_context(execution_id, context).await
550    }
551
552    async fn active_context(&self, execution_id: &str) -> Result<ToolContext, String> {
553        self.contexts
554            .lock()
555            .await
556            .get(execution_id)
557            .cloned()
558            .ok_or_else(|| format!("Execution \"{execution_id}\" does not have an active pass"))
559    }
560
561    async fn session_with_context(
562        &self,
563        execution_id: &str,
564        context: ToolContext,
565    ) -> Result<Arc<DispatchSession>, String> {
566        let state = self.require(execution_id).await?;
567        let descriptions = if let Some(capabilities) = state.capabilities {
568            capabilities.connectors
569        } else {
570            let mut descriptions = Vec::new();
571            for connector in &self.connectors {
572                descriptions.push(connector.describe().await?);
573            }
574            descriptions
575        };
576        Ok(Arc::new(
577            DispatchSession::new_with_descriptions_and_context(
578                Arc::clone(&self.runtime),
579                context,
580                self.connectors.clone(),
581                descriptions,
582            )
583            .await?,
584        ))
585    }
586
587    fn context(&self, execution_id: &str, options: CodeModeRunOptions) -> ToolContext {
588        ToolContext {
589            execution_id: execution_id.to_string(),
590            control: ToolCallControl {
591                cancellation: options.cancellation,
592                events: Some(Arc::new(RuntimeEventSink {
593                    runtime: Arc::clone(&self.runtime),
594                    execution_id: execution_id.to_string(),
595                    clock: Arc::clone(&self.clock),
596                })),
597            },
598            request: options.request,
599        }
600    }
601
602    async fn require(&self, execution_id: &str) -> Result<ExecutionState, String> {
603        self.runtime
604            .execution(execution_id)
605            .await
606            .map_err(|error| error.to_string())?
607            .ok_or_else(|| format!("Execution \"{execution_id}\" not found"))
608    }
609
610    async fn connector(&self, name: &str) -> Result<Option<&Arc<dyn Connector>>, String> {
611        for connector in &self.connectors {
612            if connector.describe().await?.name == name {
613                return Ok(Some(connector));
614            }
615        }
616        Ok(None)
617    }
618
619    async fn notify_execution_end(&self, execution_id: &str, status: &str) {
620        for connector in &self.connectors {
621            connector.execution_ended(execution_id, status).await;
622        }
623    }
624}
625
626#[cfg(test)]
627mod tests {
628    use std::sync::atomic::{AtomicUsize, Ordering};
629
630    use serde_json::json;
631    use tokio::sync::Notify;
632
633    use super::*;
634    use crate::{
635        ConnectorDescription, ConnectorTool, ExecuteResult, MemoryStore, ReplayPolicy,
636        ToolAnnotations, ToolPolicy,
637    };
638
639    struct TestConnector {
640        calls: AtomicUsize,
641        ended: Mutex<Vec<String>>,
642        started: Notify,
643        release: Notify,
644        blocked: bool,
645    }
646
647    impl TestConnector {
648        fn new(blocked: bool) -> Self {
649            Self {
650                calls: AtomicUsize::new(0),
651                ended: Mutex::new(Vec::new()),
652                started: Notify::new(),
653                release: Notify::new(),
654                blocked,
655            }
656        }
657    }
658
659    #[async_trait]
660    impl Connector for TestConnector {
661        async fn describe(&self) -> Result<ConnectorDescription, String> {
662            Ok(ConnectorDescription {
663                name: "test".to_string(),
664                instructions: None,
665                tools: vec![ConnectorTool {
666                    name: "run".to_string(),
667                    description: None,
668                    input_schema: json!({"type": "object"}),
669                    output_schema: Some(json!({"type": "integer"})),
670                    instructions: None,
671                    examples: Vec::new(),
672                    annotations: ToolAnnotations {
673                        read_only: Some(true),
674                        ..ToolAnnotations::default()
675                    },
676                    policy: ToolPolicy {
677                        requires_approval: false,
678                        replay: ReplayPolicy::Log,
679                    },
680                }],
681            })
682        }
683
684        async fn execute(
685            &self,
686            _method: &str,
687            _arguments: Value,
688            _context: &ToolContext,
689        ) -> Result<Value, String> {
690            self.calls.fetch_add(1, Ordering::SeqCst);
691            self.started.notify_one();
692            if self.blocked {
693                self.release.notified().await;
694            }
695            Ok(json!(42))
696        }
697
698        async fn execution_ended(&self, _execution_id: &str, status: &str) {
699            self.ended.lock().await.push(status.to_string());
700        }
701    }
702
703    struct HostExecutor;
704
705    #[async_trait(?Send)]
706    impl CodeExecutor for HostExecutor {
707        async fn execute(
708            &self,
709            _code: &str,
710            _connectors: &[ConnectorDescription],
711            _execution_id: &str,
712            host: Arc<ExecutionHost>,
713        ) -> Result<ExecuteResult, String> {
714            let response = host.call(0, "test", "run", json!({})).await;
715            Ok(ExecuteResult {
716                result: response.result,
717                error: response.message,
718                logs: Vec::new(),
719            })
720        }
721    }
722
723    #[tokio::test(flavor = "current_thread")]
724    async fn dispatch_requires_an_active_pass() {
725        let connector = Arc::new(TestConnector::new(false));
726        let code_mode = CodeMode::new(
727            Arc::new(MemoryStore::default()),
728            HostExecutor,
729            vec![connector.clone()],
730        );
731        let state = code_mode.start("ignored").await.unwrap();
732
733        let error = code_mode
734            .dispatch(DispatchRequest::Call {
735                execution_id: state.id.clone(),
736                seq: 0,
737                connector: "test".to_string(),
738                method: "run".to_string(),
739                arguments: json!({}),
740            })
741            .await
742            .unwrap_err();
743
744        assert!(error.contains("active pass"));
745        assert_eq!(connector.calls.load(Ordering::SeqCst), 0);
746        assert_eq!(
747            code_mode.execution(&state.id).await.unwrap().status,
748            ExecutionStatus::Running
749        );
750    }
751
752    #[tokio::test(flavor = "current_thread")]
753    async fn cancelling_an_idle_execution_ends_connector_lifecycle_once() {
754        let connector = Arc::new(TestConnector::new(false));
755        let code_mode = CodeMode::new(
756            Arc::new(MemoryStore::default()),
757            HostExecutor,
758            vec![connector.clone()],
759        );
760        let state = code_mode.start("ignored").await.unwrap();
761
762        code_mode.cancel(&state.id).await.unwrap();
763        code_mode.cancel(&state.id).await.unwrap();
764
765        assert_eq!(&*connector.ended.lock().await, &["cancelled"]);
766    }
767
768    #[tokio::test(flavor = "current_thread")]
769    async fn concurrent_drive_calls_execute_one_pass() {
770        let connector = Arc::new(TestConnector::new(true));
771        let code_mode = CodeMode::new(
772            Arc::new(MemoryStore::default()),
773            HostExecutor,
774            vec![connector.clone()],
775        );
776        let state = code_mode.start("ignored").await.unwrap();
777        let first = code_mode.drive_with(&state.id, CodeModeRunOptions::default());
778        let second = code_mode.drive_with(&state.id, CodeModeRunOptions::default());
779        let release = async {
780            connector.started.notified().await;
781            tokio::task::yield_now().await;
782            connector.release.notify_waiters();
783        };
784
785        let (first, second, ()) = tokio::join!(first, second, release);
786
787        assert_eq!(first.unwrap().status, ExecutionStatus::Completed);
788        assert_eq!(second.unwrap().status, ExecutionStatus::Completed);
789        assert_eq!(connector.calls.load(Ordering::SeqCst), 1);
790    }
791
792    #[tokio::test(flavor = "current_thread")]
793    async fn rollback_rejects_live_executions() {
794        let code_mode = CodeMode::new(
795            Arc::new(MemoryStore::default()),
796            HostExecutor,
797            vec![Arc::new(TestConnector::new(false))],
798        );
799        let state = code_mode.start("ignored").await.unwrap();
800
801        let error = code_mode.rollback(&state.id).await.unwrap_err();
802
803        assert!(error.contains("not rollback eligible"));
804        assert_eq!(
805            code_mode.execution(&state.id).await.unwrap().status,
806            ExecutionStatus::Running
807        );
808    }
809}