Replace new_session free fn with ApiSession::new; rename transport vars to session
Move token minting into ApiSession::new (in quire-core) so the
constructor is self-contained. Rename all variable bindings from
transport/t to session/s throughout.

https://claude.ai/code/session_01Ngf4zbFf87zJFUR5ee2Y8b
change
commit 096c610d6409171bee9ba6c0cca4c575da98d80e
author Claude <noreply@anthropic.com>
date
parent 5422f68e
diff --git a/quire-core/Cargo.toml b/quire-core/Cargo.toml
index 9c9763e..f48944a 100644
--- a/quire-core/Cargo.toml
+++ b/quire-core/Cargo.toml
@@ -10,6 +10,7 @@ jiff = { workspace = true }
 miette = { workspace = true }
 mlua = { workspace = true }
 petgraph = { workspace = true }
+rand = "*"
 regex = { workspace = true }
 sentry = { workspace = true }
 sentry-tracing = { workspace = true }
diff --git a/quire-core/src/ci/transport.rs b/quire-core/src/ci/transport.rs
index 85c23a1..53d8579 100644
--- a/quire-core/src/ci/transport.rs
+++ b/quire-core/src/ci/transport.rs
@@ -4,6 +4,9 @@
 //! constructs an `ApiSession` per run (minting the auth token);
 //! quire-ci reconstructs it from the `QUIRE__*` environment variables.
 
+use rand::distr::Alphanumeric;
+use rand::Rng as _;
+
 /// Credentials and endpoint coordinates for a single CI run's API
 /// channel. Holds everything quire-ci needs to call back to the
 /// server about *this* run: which run, where the server is, and
@@ -17,3 +20,18 @@ pub struct ApiSession {
     /// identifier — the server looks up the run by this token.
     pub run_token: String,
 }
+
+impl ApiSession {
+    /// Mint a fresh session for a new orchestrator-dispatched run.
+    /// Generates a CSPRNG bearer token and derives the loopback URL from `port`.
+    pub fn new(port: u16) -> Self {
+        Self {
+            server_url: format!("http://127.0.0.1:{port}"),
+            run_token: rand::rng()
+                .sample_iter(&Alphanumeric)
+                .take(32)
+                .map(char::from)
+                .collect(),
+        }
+    }
+}
diff --git a/quire-server/src/ci/mod.rs b/quire-server/src/ci/mod.rs
index f16479d..844c4eb 100644
--- a/quire-server/src/ci/mod.rs
+++ b/quire-server/src/ci/mod.rs
@@ -11,9 +11,7 @@ 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::{pipeline, registration, runtime};
-pub use run::{
-    Executor, Run, RunState, Runs, materialize_workspace, new_session, reconcile_orphans,
-};
+pub use run::{Executor, Run, RunState, Runs, materialize_workspace, reconcile_orphans};
 
 /// A resolved commit reference.
 ///
@@ -181,7 +179,7 @@ fn run_ref(
     trace_id: sentry::protocol::TraceId,
     span_id: sentry::protocol::SpanId,
 ) {
-    let transport = new_session(ctx.port);
+    let session = ApiSession::new(ctx.port);
     let sentry_trace_id = ctx.sentry_dsn.as_ref().map(|_| trace_id.to_string());
     sentry::with_scope(
         |scope| {
@@ -200,7 +198,7 @@ fn run_ref(
                 &ctx.run,
                 pushed_at,
                 push_ref,
-                &transport,
+                &session,
                 sentry_trace_id.as_deref(),
                 ctx.sentry_dsn.as_deref(),
             ) {
@@ -220,7 +218,7 @@ fn run_ref_inner(
     ctx: &RunContext<'_>,
     pushed_at: jiff::Timestamp,
     push_ref: &PushRef,
-    transport: &ApiSession,
+    session: &ApiSession,
     sentry_trace_id: Option<&str>,
     sentry_dsn: Option<&str>,
 ) -> error::Result<()> {
@@ -236,7 +234,7 @@ fn run_ref_inner(
         pushed_at,
     };
 
-    let run = ctx.repo.runs(ctx.db_path).create(&meta, Some(transport))?;
+    let run = ctx.repo.runs(ctx.db_path).create(&meta, Some(session))?;
 
     tracing::info!(
         run_id = %run.id(), // cov-excl-line
@@ -256,7 +254,7 @@ fn run_ref_inner(
                 &workspace,
                 sentry_trace_id,
                 sentry_dsn,
-                Some(transport),
+                Some(session),
             )?;
         }
     }
@@ -502,7 +500,7 @@ exit 0
         let db_path = quire.db_path();
         let ctx = run_ctx(&repo, &db_path);
         let trigger_result = with_path(&fake_path, || {
-            run_ref_inner(&ctx, pushed_at, &push_ref, &new_session(3000), None, None)
+            run_ref_inner(&ctx, pushed_at, &push_ref, &ApiSession::new(3000), None, None)
         });
 
         trigger_result.expect("trigger_ref should succeed with fake quire-ci");
@@ -550,7 +548,7 @@ exit 0
         let db_path = quire.db_path();
         let ctx = run_ctx(&repo, &db_path);
         let trigger_result = with_path(&fake_path, || {
-            run_ref_inner(&ctx, pushed_at, &push_ref, &new_session(3000), None, None)
+            run_ref_inner(&ctx, pushed_at, &push_ref, &ApiSession::new(3000), None, None)
         });
 
         let err = trigger_result.expect_err("should fail when quire-ci exits nonzero");
@@ -587,7 +585,7 @@ exit 0
 
         let db_path = quire.db_path();
         let ctx = run_ctx(&repo, &db_path);
-        run_ref_inner(&ctx, pushed_at, &push_ref, &new_session(3000), None, None)
+        run_ref_inner(&ctx, pushed_at, &push_ref, &ApiSession::new(3000), None, None)
             .expect("should succeed without ci.fnl");
     }
 
diff --git a/quire-server/src/ci/run.rs b/quire-server/src/ci/run.rs
index fc52f43..8d6a332 100644
--- a/quire-server/src/ci/run.rs
+++ b/quire-server/src/ci/run.rs
@@ -10,8 +10,6 @@ use std::path::{Path, PathBuf};
 
 use jiff::Timestamp;
 use quire_core::ci::transport::ApiSession;
-use rand::{Rng, distr::Alphanumeric};
-
 use super::error::{Error, Result};
 
 pub use quire_core::ci::run::RunMeta;
@@ -29,15 +27,6 @@ pub enum Executor {
     Process,
 }
 
-/// Mint a fresh session for a new orchestrator-dispatched run. Generates
-/// a CSPRNG bearer token, deriving the loopback server URL from `port`.
-pub fn new_session(port: u16) -> ApiSession {
-    ApiSession {
-        server_url: format!("http://127.0.0.1:{port}"),
-        run_token: mint_run_token(),
-    }
-}
-
 /// The state of a CI run.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum RunState {
@@ -107,16 +96,16 @@ impl Runs {
     /// Inserts a row into `runs` and creates the run directory for
     /// workspace materialization and log storage.
     ///
-    /// `transport` is `Some` for orchestrator-dispatched runs — the run's id
+    /// `session` 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<&ApiSession>) -> Result<Run> {
-        let (id, run_token_str) = match transport {
+    pub fn create(&self, meta: &RunMeta, session: Option<&ApiSession>) -> Result<Run> {
+        let (id, run_token_str) = match session {
             None => (uuid::Uuid::now_v7().to_string(), None),
-            Some(t) => {
+            Some(s) => {
                 let id = uuid::Uuid::now_v7().to_string();
-                (id, Some(t.run_token.as_str()))
+                (id, Some(s.run_token.as_str()))
             }
         };
         let workspace_path = self.base_dir.join(&id).join("workspace");
@@ -297,15 +286,15 @@ impl Run {
         workspace: &Path,
         sentry_trace_id: Option<&str>,
         sentry_dsn: Option<&str>,
-        transport: Option<&ApiSession>,
+        session: Option<&ApiSession>,
     ) -> Result<()> {
-        // For API transport the GET /api/run/bootstrap endpoint owns the
+        // For API runs the GET /api/run/bootstrap endpoint owns the
         // pending → active transition (it sets started_at_ms when quire-ci
         // fetches the payload). Calling transition() here would set state =
         // 'active' in the DB before quire-ci connects, causing the endpoint
         // to return 410 Gone. Update local state only so the later
         // transition(Complete/Failed) call passes the state-machine check.
-        if transport.is_some() {
+        if session.is_some() {
             self.state = RunState::Active;
         } else {
             self.transition(RunState::Active, None)?;
@@ -335,14 +324,14 @@ impl Run {
             .arg("--events")
             .arg(&events_path);
 
-        match transport {
+        match session {
             None => {
                 cmd.arg("--local").arg("--git-dir").arg(git_dir);
             }
-            Some(t) => {
+            Some(s) => {
                 self.store_bootstrap_data(git_dir, sentry_trace_id)?;
-                cmd.env("QUIRE__SERVER_URL", &t.server_url);
-                cmd.env("QUIRE__RUN_TOKEN", &t.run_token);
+                cmd.env("QUIRE__SERVER_URL", &s.server_url);
+                cmd.env("QUIRE__RUN_TOKEN", &s.run_token);
             }
         }
         if let Some(dsn) = sentry_dsn {
@@ -658,19 +647,6 @@ pub fn materialize_workspace(git_dir: &Path, sha: &str, workspace: &Path) -> Res
     Ok(())
 }
 
-/// Mint a 32-character alphanumeric bearer token from the OS CSPRNG.
-///
-/// ~190 bits of entropy, opaque to the holder. Used as the per-run
-/// auth secret for the API transport; stored in the `runs.run_token`
-/// column and passed to quire-ci via `QUIRE__RUN_TOKEN`.
-fn mint_run_token() -> String {
-    rand::rng()
-        .sample_iter(&Alphanumeric)
-        .take(32)
-        .map(char::from)
-        .collect()
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -692,7 +668,7 @@ mod tests {
     }
 
     fn test_session() -> ApiSession {
-        new_session(3000)
+        ApiSession::new(3000)
     }
 
     fn test_meta() -> RunMeta {
@@ -836,10 +812,10 @@ mod tests {
     fn create_persists_minted_run_token() {
         let (_dir, quire) = tmp_quire();
         let runs = test_runs(&quire);
-        let transport = new_session(3000);
-        let run = runs.create(&test_meta(), Some(&transport)).expect("create");
+        let session = ApiSession::new(3000);
+        let run = runs.create(&test_meta(), Some(&session)).expect("create");
 
-        // Run ID is minted by the server, not taken from the transport.
+        // Run ID is minted by the server, not taken from the session.
         assert!(uuid::Uuid::parse_str(run.id()).is_ok());
 
         let conn = crate::db::open(&quire.db_path()).expect("db");
@@ -852,13 +828,13 @@ mod tests {
             .expect("row");
         assert_eq!(
             stored.as_deref(),
-            Some(transport.run_token.as_str())
+            Some(session.run_token.as_str())
         );
     }
 
     #[test]
     fn new_session_mints_alphanumeric_token() {
-        let session = new_session(3000);
+        let session = ApiSession::new(3000);
         assert_eq!(session.server_url, "http://127.0.0.1:3000");
         assert_eq!(session.run_token.len(), 32);
         assert!(
@@ -869,9 +845,9 @@ mod tests {
     }
 
     #[test]
-    fn mint_run_token_returns_unique_values() {
-        let a = mint_run_token();
-        let b = mint_run_token();
+    fn new_session_tokens_are_unique() {
+        let a = ApiSession::new(3000).run_token;
+        let b = ApiSession::new(3000).run_token;
         assert_ne!(a, b, "two mints should not collide");
     }
 
diff --git a/quire-server/src/quire/web/api.rs b/quire-server/src/quire/web/api.rs
index 523cfe9..baaf468 100644
--- a/quire-server/src/quire/web/api.rs
+++ b/quire-server/src/quire/web/api.rs
@@ -232,7 +232,7 @@ mod tests {
     use tower::ServiceExt;
 
     use crate::Quire;
-    use crate::ci::{RunMeta, Runs, new_session};
+    use crate::ci::{ApiSession, RunMeta, Runs};
 
     struct TestEnv {
         _dir: tempfile::TempDir,
@@ -279,13 +279,13 @@ mod tests {
 
     async fn create_run_with_bootstrap(
         env: &TestEnv,
-        transport: &crate::ci::ApiSession,
+        session: &ApiSession,
         git_dir: &str,
         sentry_trace_id: Option<&str>,
     ) -> String {
         let run = env
             .runs()
-            .create(&TestEnv::meta(), Some(transport))
+            .create(&TestEnv::meta(), Some(session))
             .expect("create run");
         let run_id = run.id().to_string();
 
@@ -301,8 +301,8 @@ mod tests {
     #[tokio::test]
     async fn bootstrap_returns_401_without_auth() {
         let env = TestEnv::new();
-        let transport = new_session(3000);
-        create_run_with_bootstrap(&env, &transport, "/repos/test.git", None).await;
+        let session = ApiSession::new(3000);
+        create_run_with_bootstrap(&env, &session, "/repos/test.git", None).await;
 
         let resp = get(env.app(), "/run/bootstrap", None).await;
         assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
@@ -319,13 +319,13 @@ mod tests {
     #[tokio::test]
     async fn bootstrap_returns_payload_on_first_fetch() {
         let env = TestEnv::new();
-        let transport = new_session(3000);
-        create_run_with_bootstrap(&env, &transport, "/repos/test.git", None).await;
+        let session = ApiSession::new(3000);
+        create_run_with_bootstrap(&env, &session, "/repos/test.git", None).await;
 
         let resp = get(
             env.app(),
             "/run/bootstrap",
-            Some(&transport.run_token),
+            Some(&session.run_token),
         )
         .await;
         assert_eq!(resp.status(), StatusCode::OK);
@@ -339,9 +339,9 @@ mod tests {
     #[tokio::test]
     async fn bootstrap_returns_410_on_second_fetch() {
         let env = TestEnv::new();
-        let transport = new_session(3000);
-        create_run_with_bootstrap(&env, &transport, "/repos/test.git", None).await;
-        let token = &transport.run_token;
+        let session = ApiSession::new(3000);
+        create_run_with_bootstrap(&env, &session, "/repos/test.git", None).await;
+        let token = &session.run_token;
 
         let first = get(env.app(), "/run/bootstrap", Some(token)).await;
         assert_eq!(first.status(), StatusCode::OK);
@@ -353,9 +353,9 @@ mod tests {
     #[tokio::test]
     async fn secret_returns_401_without_auth() {
         let env = TestEnv::new();
-        let transport = new_session(3000);
+        let session = ApiSession::new(3000);
         env.runs()
-            .create(&TestEnv::meta(), Some(&transport))
+            .create(&TestEnv::meta(), Some(&session))
             .expect("create");
 
         let resp = get(env.app(), "/run/secrets/my_secret", None).await;
@@ -370,15 +370,15 @@ mod tests {
             r#"{:secrets {:my_token "hunter2"}}"#,
         )
         .expect("write config");
-        let transport = new_session(3000);
+        let session = ApiSession::new(3000);
         env.runs()
-            .create(&TestEnv::meta(), Some(&transport))
+            .create(&TestEnv::meta(), Some(&session))
             .expect("create");
 
         let resp = get(
             env.app(),
             "/run/secrets/my_token",
-            Some(&transport.run_token),
+            Some(&session.run_token),
         )
         .await;
         assert_eq!(resp.status(), StatusCode::OK);