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
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);