Use Option<Transport> for local CI runs
Local `quire ci run` passes None to Runs::create and
execute_via_quire_ci. None mints a fresh UUID without storing an auth
token and dispatches quire-ci without session flags. Orchestrator paths
continue to pass Some(&transport) as before.
change
commit 38409e7a2d3f5298ce76a1a666972fe09ec6519b
author Claude <noreply@anthropic.com>
date
parent 5c6d62b6
diff --git a/quire-server/src/bin/quire/commands/ci.rs b/quire-server/src/bin/quire/commands/ci.rs
index 56ea9ed..746f0cf 100644
--- a/quire-server/src/bin/quire/commands/ci.rs
+++ b/quire-server/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, Transport};
+use quire::ci::{Ci, CommitRef, RunMeta, Runs};
 
 /// Validate a repo's ci.fnl without executing any jobs.
 ///
@@ -74,8 +74,7 @@ pub async fn run(quire: &Quire, maybe_sha: Option<&str>) -> Result<()> {
         pushed_at: jiff::Timestamp::now(),
     };
 
-    let transport = Transport::local();
-    let run = runs.create(&meta, &transport)?;
+    let run = runs.create(&meta, None)?;
     let run_id = run.id().to_string();
     println!(
         "Run {}: executing at {} ({})",
@@ -93,7 +92,7 @@ pub async fn run(quire: &Quire, maybe_sha: Option<&str>) -> Result<()> {
         &meta,
         &secrets,
         None,
-        &transport,
+        None,
     );
 
     // Print the combined quire-ci log regardless of outcome.
diff --git a/quire-server/src/ci/mod.rs b/quire-server/src/ci/mod.rs
index 76d827b..da06644 100644
--- a/quire-server/src/ci/mod.rs
+++ b/quire-server/src/ci/mod.rs
@@ -247,7 +247,7 @@ fn run_ref_inner(
         pushed_at,
     };
 
-    let run = ctx.repo.runs(ctx.db_path).create(&meta, transport)?;
+    let run = ctx.repo.runs(ctx.db_path).create(&meta, Some(transport))?;
 
     tracing::info!(
         run_id = %run.id(), // cov-excl-line
@@ -268,7 +268,7 @@ fn run_ref_inner(
                 &meta,
                 ctx.secrets,
                 sentry,
-                transport,
+                Some(transport),
             )?;
         }
     }
diff --git a/quire-server/src/ci/run.rs b/quire-server/src/ci/run.rs
index 2fa38b1..400a7a6 100644
--- a/quire-server/src/ci/run.rs
+++ b/quire-server/src/ci/run.rs
@@ -42,23 +42,15 @@ pub enum TransportMode {
 /// Runtime transport for a single CI run. Built once per run from
 /// the config-shape [`TransportMode`] + the server's listen port,
 /// then passed to `Runs::create` and `Run::execute_via_quire_ci`.
-/// `Filesystem` and `Api` carry an [`ApiSession`] so quire-ci receives
-/// session info for API route use. `Local` carries no session and is
-/// used for `quire ci run` where no server is involved.
+/// Both variants carry an [`ApiSession`] so quire-ci always receives
+/// session info. Use `None` for local runs where no server is involved.
 #[derive(Clone, Debug)]
 pub enum Transport {
-    Local,
     Filesystem(ApiSession),
     Api(ApiSession),
 }
 
 impl Transport {
-    /// Build a transport for a local `quire ci run` invocation.
-    /// No session info is minted — quire-ci runs without transport flags.
-    pub fn local() -> Self {
-        Transport::Local
-    }
-
     /// Build a runtime transport for a new run. Always mints a fresh
     /// run ID and CSPRNG bearer token, deriving the loopback server
     /// URL from `port`. The variant controls whether quire-ci uses
@@ -145,14 +137,14 @@ impl Runs {
     /// Inserts a row into `runs` and creates the run directory for
     /// workspace materialization and log storage.
     ///
-    /// `transport` is built by the caller via [`Transport::for_new_run`].
-    /// The run's id and bearer token come from the transport's [`ApiSession`]
-    /// in both variants — both are persisted so quire-ci and the DB always
-    /// agree on which run a token belongs to.
-    pub fn create(&self, meta: &RunMeta, transport: &Transport) -> Result<Run> {
+    /// `transport` is `Some` for orchestrator-dispatched runs — the run's id
+    /// and bearer token come from the session and are persisted so quire-ci
+    /// and the DB agree. Pass `None` for local runs; a fresh UUID is minted
+    /// and no auth token is stored.
+    pub fn create(&self, meta: &RunMeta, transport: Option<&Transport>) -> Result<Run> {
         let (id, auth_token_str) = match transport {
-            Transport::Local => (uuid::Uuid::now_v7().to_string(), None),
-            Transport::Filesystem(api) | Transport::Api(api) => {
+            None => (uuid::Uuid::now_v7().to_string(), None),
+            Some(Transport::Filesystem(api) | Transport::Api(api)) => {
                 (api.run_id.clone(), Some(api.auth_token.as_str()))
             }
         };
@@ -339,7 +331,7 @@ impl Run {
         meta: &RunMeta,
         secrets: &HashMap<String, SecretString>,
         sentry: Option<&quire_core::ci::bootstrap::SentryHandoff>,
-        transport: &Transport,
+        transport: Option<&Transport>,
     ) -> Result<()> {
         self.transition(RunState::Active, None)?;
 
@@ -365,7 +357,7 @@ impl Run {
         cmd.arg("run").arg("--workspace").arg(workspace);
 
         match transport {
-            Transport::Local => {
+            None => {
                 cmd.arg("--out-dir")
                     .arg(&run_dir)
                     .arg("--events")
@@ -373,7 +365,7 @@ impl Run {
                     .arg("--bootstrap")
                     .arg(&bootstrap_path);
             }
-            Transport::Filesystem(api) => {
+            Some(Transport::Filesystem(api)) => {
                 cmd.arg("--run-id")
                     .arg(&api.run_id)
                     .arg("--server-url")
@@ -386,7 +378,7 @@ impl Run {
                     .arg("--bootstrap")
                     .arg(&bootstrap_path);
             }
-            Transport::Api(api) => {
+            Some(Transport::Api(api)) => {
                 cmd.arg("--run-id")
                     .arg(&api.run_id)
                     .arg("--server-url")
@@ -939,7 +931,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         let parsed = uuid::Uuid::parse_str(run.id()).expect("should be valid UUID");
         assert_eq!(parsed.get_version(), Some(uuid::Version::SortRand));
@@ -950,7 +942,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let transport = Transport::for_new_run(TransportMode::Filesystem, 3000);
-        let run = runs.create(&test_meta(), &transport).expect("create");
+        let run = runs.create(&test_meta(), Some(&transport)).expect("create");
 
         let Transport::Filesystem(api) = &transport else {
             panic!("expected Filesystem transport");
@@ -979,7 +971,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let transport = Transport::for_new_run(TransportMode::Api, 3000);
-        let run = runs.create(&test_meta(), &transport).expect("create");
+        let run = runs.create(&test_meta(), Some(&transport)).expect("create");
 
         let Transport::Api(api) = &transport else {
             panic!("expected Api transport");
@@ -1013,7 +1005,6 @@ mod tests {
             ),
         ] {
             let api = match &transport {
-                Transport::Local => unreachable!("test transports carry sessions"),
                 Transport::Filesystem(api) | Transport::Api(api) => api,
             };
             assert_eq!(api.server_url, expected_url);
@@ -1043,7 +1034,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
 
         assert_eq!(run.state(), RunState::Pending);
@@ -1068,7 +1059,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         let id = run.id().to_string();
 
@@ -1091,7 +1082,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
 
         run.transition(RunState::Active, None).expect("to active");
@@ -1106,7 +1097,7 @@ mod tests {
         let runs = test_runs(&quire);
 
         let mut completed = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         completed
             .transition(RunState::Active, None)
@@ -1117,7 +1108,7 @@ mod tests {
         assert!(completed.read_finished_at().expect("read").is_some());
 
         let mut failed = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         failed
             .transition(RunState::Active, None)
@@ -1133,7 +1124,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         let id = run.id().to_string();
 
@@ -1157,7 +1148,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         let id = run.id().to_string();
 
@@ -1182,13 +1173,13 @@ mod tests {
 
         // Pending -> Failed is not allowed (must go via Active).
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         assert!(run.transition(RunState::Failed, None).is_err());
 
         // Terminal -> anything is not allowed.
         let mut completed = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         completed
             .transition(RunState::Active, None)
@@ -1205,7 +1196,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
 
         run.transition(RunState::Active, None).expect("to active");
@@ -1225,7 +1216,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
 
         run.transition(RunState::Active, None).expect("to active");
@@ -1240,7 +1231,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         let id = run.id().to_string();
 
@@ -1255,7 +1246,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         run.transition(RunState::Active, None).expect("to active");
         let id = run.id().to_string();
@@ -1271,7 +1262,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         run.transition(RunState::Active, None).expect("to active");
         run.transition(RunState::Complete, None)
@@ -1297,7 +1288,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         let run_id = run.id().to_string();
 
@@ -1411,7 +1402,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
 
         let missing = run.path().join("events.jsonl");
@@ -1438,7 +1429,7 @@ mod tests {
 
         // Create first run.
         let run1 = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create run1");
         let run1_id = run1.id().to_string();
         assert_eq!(run1.state(), RunState::Pending);
@@ -1449,7 +1440,7 @@ mod tests {
             r#ref: "refs/heads/main".to_string(),
             pushed_at: "2026-04-28T13:00:00Z".parse().unwrap(),
         };
-        let run2 = runs.create(&meta2, &test_transport()).expect("create run2");
+        let run2 = runs.create(&meta2, Some(&test_transport())).expect("create run2");
         assert_eq!(run2.state(), RunState::Pending);
 
         // First run should now be superseded.
@@ -1468,7 +1459,7 @@ mod tests {
 
         // Create and activate first run.
         let mut run1 = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create run1");
         let run1_id = run1.id().to_string();
         run1.transition(RunState::Active, None).expect("to active");
@@ -1479,7 +1470,7 @@ mod tests {
             r#ref: "refs/heads/main".to_string(),
             pushed_at: "2026-04-28T13:00:00Z".parse().unwrap(),
         };
-        let run2 = runs.create(&meta2, &test_transport()).expect("create run2");
+        let run2 = runs.create(&meta2, Some(&test_transport())).expect("create run2");
         assert_eq!(run2.state(), RunState::Pending);
 
         // First run should be superseded.
@@ -1498,7 +1489,7 @@ mod tests {
 
         // Create run for main.
         let run1 = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create run1");
         let run1_id = run1.id().to_string();
 
@@ -1508,7 +1499,7 @@ mod tests {
             r#ref: "refs/heads/feature".to_string(),
             pushed_at: "2026-04-28T13:00:00Z".parse().unwrap(),
         };
-        let _run2 = runs.create(&meta2, &test_transport()).expect("create run2");
+        let _run2 = runs.create(&meta2, Some(&test_transport())).expect("create run2");
 
         // First run should still be pending.
         let reopened = Run::open(quire.db_path(), run1_id, runs.base_dir.clone()).expect("reopen");
@@ -1522,7 +1513,7 @@ mod tests {
 
         // Create and complete first run.
         let mut run1 = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create run1");
         let run1_id = run1.id().to_string();
         run1.transition(RunState::Active, None).expect("to active");
@@ -1535,7 +1526,7 @@ mod tests {
             r#ref: "refs/heads/main".to_string(),
             pushed_at: "2026-04-28T13:00:00Z".parse().unwrap(),
         };
-        let _run2 = runs.create(&meta2, &test_transport()).expect("create run2");
+        let _run2 = runs.create(&meta2, Some(&test_transport())).expect("create run2");
 
         // First run should still be complete.
         let reopened = Run::open(quire.db_path(), run1_id, runs.base_dir.clone()).expect("reopen");
@@ -1547,7 +1538,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         run.transition(RunState::Superseded, None)
             .expect("to superseded");
@@ -1559,7 +1550,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         run.transition(RunState::Active, None).expect("to active");
         run.transition(RunState::Superseded, None)
@@ -1572,7 +1563,7 @@ mod tests {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
         let mut run = runs
-            .create(&test_meta(), &test_transport())
+            .create(&test_meta(), Some(&test_transport()))
             .expect("create");
         run.transition(RunState::Active, None).expect("to active");
 
diff --git a/quire-server/src/quire/web/api.rs b/quire-server/src/quire/web/api.rs
index b4056cb..d8b5260 100644
--- a/quire-server/src/quire/web/api.rs
+++ b/quire-server/src/quire/web/api.rs
@@ -169,7 +169,7 @@ mod tests {
         let env = TestEnv::new();
         let transport = Transport::for_new_run(TransportMode::Api, 3000);
         env.runs()
-            .create(&TestEnv::meta(), &transport)
+            .create(&TestEnv::meta(), Some(&transport))
             .expect("create");
         let Transport::Api(ref session) = transport else {
             panic!("expected Api transport");
@@ -185,7 +185,7 @@ mod tests {
         let env = TestEnv::new();
         let transport = Transport::for_new_run(TransportMode::Api, 3000);
         env.runs()
-            .create(&TestEnv::meta(), &transport)
+            .create(&TestEnv::meta(), Some(&transport))
             .expect("create");
         let Transport::Api(ref session) = transport else {
             panic!("expected Api transport");
@@ -213,7 +213,7 @@ mod tests {
         let env = TestEnv::new();
         let transport = Transport::for_new_run(TransportMode::Api, 3000);
         env.runs()
-            .create(&TestEnv::meta(), &transport)
+            .create(&TestEnv::meta(), Some(&transport))
             .expect("create");
         let Transport::Api(ref session) = transport else {
             panic!("expected Api transport");
@@ -235,7 +235,7 @@ mod tests {
         .expect("write config");
         let transport = Transport::for_new_run(TransportMode::Api, 3000);
         env.runs()
-            .create(&TestEnv::meta(), &transport)
+            .create(&TestEnv::meta(), Some(&transport))
             .expect("create");
         let Transport::Api(ref session) = transport else {
             panic!("expected Api transport");