Plumb Executor and workspace through Run::execute
Adds the parameters now so the per-run container lifecycle work
can wire up behavior without churning every call site again.

Assisted-by: Claude Opus 4.7 (1M context) via Claude Code
change oxznlvyvxrwyxvnmwrltsylywqsmnmvu
commit 950e7bb24a7428b9f7691c35d03bc1bcaa27079d
author Alpha Chen <alpha@kejadlen.dev>
date
parent srpvvypu
diff --git a/src/bin/quire/commands/ci.rs b/src/bin/quire/commands/ci.rs
index 8f7ee97..407525d 100644
--- a/src/bin/quire/commands/ci.rs
+++ b/src/bin/quire/commands/ci.rs
@@ -2,7 +2,7 @@ use std::path::PathBuf;
 
 use miette::{IntoDiagnostic, Result};
 use quire::Quire;
-use quire::ci::{Ci, CommitRef, RunMeta, Runs};
+use quire::ci::{Ci, CommitRef, Executor, RunMeta, Runs};
 
 /// Validate a repo's ci.fnl without executing any jobs.
 ///
@@ -76,7 +76,15 @@ pub async fn run(quire: &Quire, maybe_sha: Option<&str>) -> Result<()> {
     let run = runs.create(&meta)?;
     println!("Run {}: executing at {}", run.id(), commit.display);
 
-    let exec_result = run.execute(pipeline, secrets, &repo_path.join(".git"));
+    let workspace = tmp.path().join("workspace");
+    fs_err::create_dir_all(&workspace).into_diagnostic()?;
+    let exec_result = run.execute(
+        pipeline,
+        secrets,
+        &repo_path.join(".git"),
+        &workspace,
+        Executor::Host,
+    );
 
     match exec_result {
         Ok(outputs) => {
diff --git a/src/ci/mod.rs b/src/ci/mod.rs
index 0916015..bcaadd6 100644
--- a/src/ci/mod.rs
+++ b/src/ci/mod.rs
@@ -9,7 +9,7 @@ mod run;
 mod runtime;
 
 pub use pipeline::{DefinitionError, Diagnostic, Job, Pipeline, PipelineError, StructureError};
-pub use run::{Run, RunMeta, RunState, RunTimes, Runs};
+pub use run::{Executor, Run, RunMeta, RunState, RunTimes, Runs};
 
 /// A resolved commit reference.
 ///
@@ -174,7 +174,15 @@ fn trigger_ref(
         }
     };
 
-    run.execute(pipeline, secrets.clone(), &repo.path())?;
+    let workspace = run.path().join("workspace");
+    fs_err::create_dir_all(&workspace)?;
+    run.execute(
+        pipeline,
+        secrets.clone(),
+        &repo.path(),
+        &workspace,
+        run::Executor::Host,
+    )?;
     Ok(())
 }
 
diff --git a/src/ci/run.rs b/src/ci/run.rs
index 2513ced..a75dd94 100644
--- a/src/ci/run.rs
+++ b/src/ci/run.rs
@@ -18,6 +18,14 @@ use crate::display_chain;
 use crate::secret::SecretString;
 use crate::{Error, Result};
 
+/// The execution mode for a run. Host runs `sh` directly on the host.
+/// Docker materializes a container and routes `sh` through `docker exec`.
+#[derive(Debug, Clone)]
+pub enum Executor {
+    Host,
+    // Docker variant added in Task 5.
+}
+
 /// The state of a CI run.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum RunState {
@@ -274,7 +282,12 @@ impl Run {
         pipeline: Pipeline,
         secrets: HashMap<String, SecretString>,
         git_dir: &std::path::Path,
+        workspace: &std::path::Path,
+        executor: Executor,
     ) -> Result<HashMap<String, Vec<ShOutput>>> {
+        // `workspace` and `executor` are not yet read; later tasks
+        // wire them into the runtime and per-run container lifecycle.
+        let _ = (workspace, executor);
         let meta = self.read_meta()?;
 
         let runtime = Rc::new(Runtime::new(pipeline, secrets, &meta, git_dir));
@@ -455,6 +468,14 @@ mod tests {
         Runs::new(quire.base_dir().join("runs").join("test.git"))
     }
 
+    /// Materialize a workspace directory under the test Quire's base dir.
+    /// Used by `Run::execute` call sites to satisfy the workspace param.
+    fn test_workspace(quire: &Quire) -> PathBuf {
+        let workspace = quire.base_dir().join("ws");
+        fs_err::create_dir_all(&workspace).expect("mkdir workspace");
+        workspace
+    }
+
     fn test_meta() -> RunMeta {
         RunMeta {
             sha: "abc123".to_string(),
@@ -810,7 +831,13 @@ mod tests {
 
         let run_id = run.id().to_string();
         let outputs = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect("execute");
 
         // Verify the run landed in complete/ on disk.
@@ -843,8 +870,14 @@ mod tests {
         );
         let pipeline = load(&source);
 
-        run.execute(pipeline, HashMap::new(), std::path::Path::new("."))
-            .expect("execute");
+        run.execute(
+            pipeline,
+            HashMap::new(),
+            std::path::Path::new("."),
+            &test_workspace(&quire),
+            Executor::Host,
+        )
+        .expect("execute");
 
         let contents = fs_err::read_to_string(&log).expect("read log");
         assert_eq!(contents, "a\nb\n");
@@ -864,7 +897,13 @@ mod tests {
 
         let run_id = run.id().to_string();
         let err = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect_err("expected failure");
         assert!(matches!(err, Error::JobFailed { ref job, .. } if job == "a"));
 
@@ -888,7 +927,13 @@ mod tests {
         );
 
         let outputs = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect("execute");
 
         let grab = &outputs["grab"];
@@ -914,7 +959,13 @@ mod tests {
         );
 
         let outputs = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect("execute");
 
         let b = &outputs["b"];
@@ -934,7 +985,13 @@ mod tests {
         );
 
         let err = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect_err("expected failure");
         let Error::JobFailed { job, source } = err else {
             unreachable!()
@@ -961,7 +1018,13 @@ mod tests {
         );
 
         let err = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect_err("expected failure");
         let Error::JobFailed { source, .. } = err else {
             unreachable!()
@@ -985,7 +1048,13 @@ mod tests {
         );
 
         let err = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect_err("expected failure");
         let Error::JobFailed { source, .. } = err else {
             unreachable!()
@@ -1014,7 +1083,13 @@ mod tests {
         );
 
         let outputs = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect("execute");
         let b = &outputs["b"];
         assert_eq!(b.len(), 1);
@@ -1033,8 +1108,14 @@ mod tests {
         );
 
         let run_id = run.id().to_string();
-        run.execute(pipeline, HashMap::new(), std::path::Path::new("."))
-            .expect("execute");
+        run.execute(
+            pipeline,
+            HashMap::new(),
+            std::path::Path::new("."),
+            &test_workspace(&quire),
+            Executor::Host,
+        )
+        .expect("execute");
 
         let log_path = runs
             .base
@@ -1069,7 +1150,13 @@ mod tests {
         );
 
         let run_id = run.id().to_string();
-        let _ = run.execute(pipeline, HashMap::new(), std::path::Path::new("."));
+        let _ = run.execute(
+            pipeline,
+            HashMap::new(),
+            std::path::Path::new("."),
+            &test_workspace(&quire),
+            Executor::Host,
+        );
 
         let failed_dir = runs.base.join(RunState::Failed.dir_name()).join(&run_id);
         assert!(failed_dir.exists(), "run should be in failed/");
@@ -1102,7 +1189,13 @@ mod tests {
         );
 
         let err = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect_err("expected failure");
         let Error::JobFailed { job, source } = err else {
             panic!("expected JobFailed, got: {err:?}")
@@ -1135,8 +1228,14 @@ mod tests {
             Ok(())
         })));
 
-        run.execute(pipeline, HashMap::new(), std::path::Path::new("."))
-            .expect("execute should succeed");
+        run.execute(
+            pipeline,
+            HashMap::new(),
+            std::path::Path::new("."),
+            &test_workspace(&quire),
+            Executor::Host,
+        )
+        .expect("execute should succeed");
         assert!(called.get(), "rust run-fn should have been called");
     }
 
@@ -1156,7 +1255,13 @@ mod tests {
         })));
 
         let err = run
-            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .execute(
+                pipeline,
+                HashMap::new(),
+                std::path::Path::new("."),
+                &test_workspace(&quire),
+                Executor::Host,
+            )
             .expect_err("expected failure");
         let Error::JobFailed { job, source } = err else {
             panic!("expected JobFailed, got: {err:?}");