Make Transport optional for local CI runs
- Move Transport struct and TransportMode enum to quire-core so both
quire-ci and quire-server share the same types without duplication
- Change Run::execute and Runs::create to accept Option<&Transport>:
None = local run (no server session), Some = orchestrator-dispatched
- Remove the quire-ci local subcommand; quire ci run exercises the
real executor path including the future container executor
- Flatten Transport from enum-with-session into a plain struct with
session + mode fields, since both variants carried identical session
- Rename new-transport factory from Transport::for_new_run to free
function new_transport in quire-server (minting stays server-side)
- Rename local clap enum Transport → TransportFlag to avoid shadowing
the imported Transport from quire-core
- Remove TransportArgs; resolve() now returns core Transport directly
diff --git a/quire-ci/src/main.rs b/quire-ci/src/main.rs
index d99647d..1847d71 100644
--- a/quire-ci/src/main.rs
+++ b/quire-ci/src/main.rs
@@ -12,7 +12,7 @@ use quire_core::ci::event::{Event, EventKind, JobOutcome, RunOutcome};
use quire_core::ci::pipeline::{self, Pipeline, RunFn};
use quire_core::ci::run::RunMeta;
use quire_core::ci::runtime::{Runtime, RuntimeError, RuntimeEvent, RuntimeHandle};
-use quire_core::ci::transport::ApiSession;
+use quire_core::ci::transport::{ApiSession, Transport, TransportMode};
use quire_core::fennel::FennelError;
use quire_core::telemetry::{self, FmtMode, MietteLayer};
@@ -96,11 +96,11 @@ struct TransportFlags {
/// Transport for CI ↔ server communication.
#[arg(long, default_value = "filesystem", value_enum)]
- transport: Transport,
+ transport: TransportFlag,
}
impl TransportFlags {
- fn resolve(self, auth_token: Option<String>) -> miette::Result<TransportArgs> {
+ fn resolve(self, auth_token: Option<String>) -> miette::Result<Transport> {
let auth_token =
auth_token.ok_or_else(|| miette::miette!("QUIRE_CI_TOKEN env var is required"))?;
let session = ApiSession {
@@ -108,10 +108,11 @@ impl TransportFlags {
server_url: self.server_url,
auth_token,
};
- Ok(TransportArgs {
- session,
- mode: self.transport,
- })
+ let mode = match self.transport {
+ TransportFlag::Filesystem => TransportMode::Filesystem,
+ TransportFlag::Api => TransportMode::Api,
+ };
+ Ok(Transport { session, mode })
}
}
@@ -186,24 +187,13 @@ fn parse_events_target(s: &str) -> Result<EventsTarget, String> {
}
}
-/// Transport for CI ↔ server communication.
-///
-/// CLI-shape only; the resolved variant (carrying the shared
-/// [`ApiSession`]) is `TransportArgs`.
+/// CLI-only transport mode selector for `--transport`.
#[derive(Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
-pub enum Transport {
+enum TransportFlag {
Filesystem,
Api,
}
-/// Resolved transport. CLI-shape [`Transport`] + session from flags and env.
-#[derive(Debug)]
-#[allow(dead_code)] // session fields read by upcoming API client
-struct TransportArgs {
- session: ApiSession,
- mode: Transport,
-}
-
fn main() -> miette::Result<()> {
miette::set_panic_hook();
let cli = Cli::parse();
@@ -389,7 +379,7 @@ fn run_pipeline(
git_dir: PathBuf,
meta: RunMeta,
secrets: HashMap<String, quire_core::secret::SecretString>,
- _transport: TransportArgs,
+ _transport: Transport,
) -> miette::Result<()> {
let pipeline = match compile_at(&workspace) {
Ok(p) => p,
diff --git a/quire-core/src/ci/transport.rs b/quire-core/src/ci/transport.rs
index b27f55a..1fac832 100644
--- a/quire-core/src/ci/transport.rs
+++ b/quire-core/src/ci/transport.rs
@@ -1,7 +1,7 @@
//! Shared transport types for CI ↔ server communication.
//!
//! The on-the-wire pairing both sides agree on. The orchestrator
-//! constructs an `ApiSession` per run (minting the auth token and
+//! constructs a `Transport` per run (minting the auth token and
//! using the run's UUID); quire-ci reconstructs it from CLI flags
//! plus `QUIRE_CI_TOKEN`.
@@ -20,3 +20,20 @@ pub struct ApiSession {
/// `runs.auth_token` server-side.
pub auth_token: String,
}
+
+/// Transport mode for CI ↔ server communication.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum TransportMode {
+ #[default]
+ Filesystem,
+ Api,
+}
+
+/// Runtime transport for a single CI run.
+/// Use `None` for local runs where no server is involved.
+#[derive(Clone, Debug)]
+pub struct Transport {
+ pub session: ApiSession,
+ pub mode: TransportMode,
+}
diff --git a/quire-server/src/ci/mod.rs b/quire-server/src/ci/mod.rs
index f6a9308..203c34e 100644
--- a/quire-server/src/ci/mod.rs
+++ b/quire-server/src/ci/mod.rs
@@ -12,10 +12,10 @@ pub use quire_core::ci::pipeline::{
};
pub use quire_core::ci::run::RunMeta;
pub use quire_core::ci::transport::ApiSession;
+pub use quire_core::ci::transport::{Transport, TransportMode};
pub use quire_core::ci::{pipeline, registration, runtime};
pub use run::{
- Executor, Run, RunState, Runs, Transport, TransportMode, materialize_workspace,
- reconcile_orphans,
+ Executor, Run, RunState, Runs, materialize_workspace, new_transport, reconcile_orphans,
};
/// A resolved commit reference.
@@ -188,7 +188,7 @@ fn run_ref(
trace_id: sentry::protocol::TraceId,
span_id: sentry::protocol::SpanId,
) {
- let transport = Transport::for_new_run(ctx.transport_mode, ctx.port);
+ let transport = new_transport(ctx.transport_mode, ctx.port);
let sentry_handoff =
ctx.sentry_dsn
.as_ref()
@@ -524,7 +524,7 @@ exit 0
&ctx,
pushed_at,
&push_ref,
- &Transport::for_new_run(TransportMode::Filesystem, 3000),
+ &new_transport(TransportMode::Filesystem, 3000),
None,
)
});
@@ -579,7 +579,7 @@ exit 0
&ctx,
pushed_at,
&push_ref,
- &Transport::for_new_run(TransportMode::Filesystem, 3000),
+ &new_transport(TransportMode::Filesystem, 3000),
None,
)
});
@@ -623,7 +623,7 @@ exit 0
&ctx,
pushed_at,
&push_ref,
- &Transport::for_new_run(TransportMode::Filesystem, 3000),
+ &new_transport(TransportMode::Filesystem, 3000),
None,
)
.expect("should succeed without ci.fnl");
diff --git a/quire-server/src/ci/run.rs b/quire-server/src/ci/run.rs
index 6b15ce5..37edde8 100644
--- a/quire-server/src/ci/run.rs
+++ b/quire-server/src/ci/run.rs
@@ -16,6 +16,7 @@ use rand::{Rng, distr::Alphanumeric};
use super::error::{Error, Result};
pub use quire_core::ci::run::RunMeta;
+pub use quire_core::ci::transport::{Transport, TransportMode};
/// How a run dispatches its pipeline.
///
@@ -30,38 +31,17 @@ pub enum Executor {
Process,
}
-/// Transport for CI ↔ server communication.
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Deserialize)]
-#[serde(rename_all = "kebab-case")]
-pub enum TransportMode {
- #[default]
- Filesystem,
- Api,
-}
-
-/// 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`.
-/// Use `None` for local runs where no server is involved.
-#[derive(Clone, Debug)]
-pub struct Transport {
- pub session: ApiSession,
- pub mode: TransportMode,
-}
-
-impl Transport {
- /// 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`.
- pub fn for_new_run(mode: TransportMode, port: u16) -> Self {
- Self {
- session: ApiSession {
- run_id: uuid::Uuid::now_v7().to_string(),
- server_url: format!("http://127.0.0.1:{port}"),
- auth_token: mint_auth_token(),
- },
- mode,
- }
+/// Mint a fresh transport for a new orchestrator-dispatched run. Generates
+/// a UUIDv7 run ID and CSPRNG bearer token, deriving the loopback server
+/// URL from `port`.
+pub fn new_transport(mode: TransportMode, port: u16) -> Transport {
+ Transport {
+ session: ApiSession {
+ run_id: uuid::Uuid::now_v7().to_string(),
+ server_url: format!("http://127.0.0.1:{port}"),
+ auth_token: mint_auth_token(),
+ },
+ mode,
}
}
@@ -768,7 +748,7 @@ mod tests {
}
fn test_transport() -> Transport {
- Transport::for_new_run(TransportMode::Filesystem, 3000)
+ new_transport(TransportMode::Filesystem, 3000)
}
fn test_meta() -> RunMeta {
@@ -937,7 +917,7 @@ mod tests {
fn create_with_filesystem_persists_auth_token() {
let (_dir, quire) = tmp_quire();
let runs = test_runs(&quire);
- let transport = Transport::for_new_run(TransportMode::Filesystem, 3000);
+ let transport = new_transport(TransportMode::Filesystem, 3000);
let run = runs.create(&test_meta(), Some(&transport)).expect("create");
assert_eq!(run.id(), transport.session.run_id);
@@ -961,7 +941,7 @@ mod tests {
fn create_with_api_persists_minted_auth_token() {
let (_dir, quire) = tmp_quire();
let runs = test_runs(&quire);
- let transport = Transport::for_new_run(TransportMode::Api, 3000);
+ let transport = new_transport(TransportMode::Api, 3000);
let run = runs.create(&test_meta(), Some(&transport)).expect("create");
assert_eq!(run.id(), transport.session.run_id);
@@ -984,11 +964,11 @@ mod tests {
fn for_new_run_mints_alphanumeric_token() {
for (transport, expected_url) in [
(
- Transport::for_new_run(TransportMode::Filesystem, 3000),
+ new_transport(TransportMode::Filesystem, 3000),
"http://127.0.0.1:3000",
),
(
- Transport::for_new_run(TransportMode::Api, 4000),
+ new_transport(TransportMode::Api, 4000),
"http://127.0.0.1:4000",
),
] {
diff --git a/quire-server/src/quire/web/api.rs b/quire-server/src/quire/web/api.rs
index 22ae24a..eacf6e3 100644
--- a/quire-server/src/quire/web/api.rs
+++ b/quire-server/src/quire/web/api.rs
@@ -119,7 +119,7 @@ mod tests {
use tower::ServiceExt;
use crate::Quire;
- use crate::ci::{RunMeta, Runs, Transport, TransportMode};
+ use crate::ci::{RunMeta, Runs, TransportMode, new_transport};
struct TestEnv {
_dir: tempfile::TempDir,
@@ -167,7 +167,7 @@ mod tests {
#[tokio::test]
async fn secret_returns_401_without_auth_header() {
let env = TestEnv::new();
- let transport = Transport::for_new_run(TransportMode::Api, 3000);
+ let transport = new_transport(TransportMode::Api, 3000);
env.runs()
.create(&TestEnv::meta(), Some(&transport))
.expect("create");
@@ -180,7 +180,7 @@ mod tests {
#[tokio::test]
async fn secret_returns_401_for_wrong_token() {
let env = TestEnv::new();
- let transport = Transport::for_new_run(TransportMode::Api, 3000);
+ let transport = new_transport(TransportMode::Api, 3000);
env.runs()
.create(&TestEnv::meta(), Some(&transport))
.expect("create");
@@ -205,7 +205,7 @@ mod tests {
#[tokio::test]
async fn secret_returns_404_for_unknown_secret_name() {
let env = TestEnv::new();
- let transport = Transport::for_new_run(TransportMode::Api, 3000);
+ let transport = new_transport(TransportMode::Api, 3000);
env.runs()
.create(&TestEnv::meta(), Some(&transport))
.expect("create");
@@ -224,7 +224,7 @@ mod tests {
r#"{:secrets {:my_token "hunter2"}}"#,
)
.expect("write config");
- let transport = Transport::for_new_run(TransportMode::Api, 3000);
+ let transport = new_transport(TransportMode::Api, 3000);
env.runs()
.create(&TestEnv::meta(), Some(&transport))
.expect("create");