Replace clap with figue and move transport creds to env vars
- quire-ci now reads QUIRE__RUN_ID, QUIRE__SERVER_URL, QUIRE__AUTH_TOKEN,
QUIRE__TRANSPORT, and QUIRE__SENTRY_DSN from the environment instead
of positional CLI flags; uses figue for layered config (CLI → env →
defaults) via #[derive(Facet)] structs
- TransportMode derives Facet so it can be used directly in the config
struct rather than as a plain String
- quire-server sets those env vars on the subprocess instead of passing
CLI flags
- SentryHandoff removed; bootstrap carries only sentry_trace_id: Option<String>
and the DSN travels separately via QUIRE__SENTRY_DSN
- facet promoted to a workspace dependency
https://claude.ai/code/session_01NFGc15XXN1PGGa2hFom7RA
diff --git a/Cargo.toml b/Cargo.toml
index 71040d8..0713734 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,7 @@ resolver = "3"
[workspace.dependencies]
clap = { version = "*", features = ["derive", "env"] }
+facet = "*"
fs-err = "*"
jiff = { version = "*", features = ["serde"] }
miette = "*"
diff --git a/quire-ci/Cargo.toml b/quire-ci/Cargo.toml
index 41fcc05..8c4db26 100644
--- a/quire-ci/Cargo.toml
+++ b/quire-ci/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
-facet = "*"
+facet = { workspace = true }
figue = "*"
fs-err = { workspace = true }
jiff = { workspace = true }
diff --git a/quire-ci/src/main.rs b/quire-ci/src/main.rs
index e00e8d5..a2b19b0 100644
--- a/quire-ci/src/main.rs
+++ b/quire-ci/src/main.rs
@@ -9,7 +9,7 @@ use facet::Facet;
use figue::{self as args, Driver, FigueBuiltins};
use miette::{IntoDiagnostic, Result};
use quire_core::api::SecretResponse;
-use quire_core::ci::bootstrap::{Bootstrap, SentryHandoff};
+use quire_core::ci::bootstrap::Bootstrap;
use quire_core::ci::event::{Event, EventKind, JobOutcome, RunOutcome};
use quire_core::ci::pipeline::{self, Pipeline, RunFn};
use quire_core::ci::run::RunMeta;
@@ -280,7 +280,7 @@ fn main() -> Result<()> {
}
};
- let (git_dir, meta, sentry_handoff) = load_bootstrap(&PathBuf::from(&bootstrap))?;
+ let (git_dir, meta, sentry_trace_id) = load_bootstrap(&PathBuf::from(&bootstrap))?;
// Sentry's reqwest transport spawns Tokio tasks for HTTP
// sends, so the client must be constructed and dropped from
@@ -296,8 +296,11 @@ fn main() -> Result<()> {
// Drop order: `_sentry` flushes first (still inside the
// runtime), then `_enter`, then `rt`.
- let trace_id = sentry_handoff.as_ref().map(|h| h.trace_id.as_str());
- let _sentry = init_sentry(cli.quire.sentry_dsn.as_deref(), trace_id, &meta);
+ let _sentry = init_sentry(
+ cli.quire.sentry_dsn.as_deref(),
+ sentry_trace_id.as_deref(),
+ &meta,
+ );
// No type registrations: quire-ci's user-level errors
// (CompileError, JobError, FennelError) are no longer logged
@@ -397,7 +400,7 @@ fn validate(workspace: PathBuf) -> Result<()> {
/// Unlinks the file as soon as the bytes are in memory — getting it off
/// disk early limits the blast radius of a later panic or crash leaving
/// a 0600 file behind.
-fn load_bootstrap(path: &std::path::Path) -> Result<(PathBuf, RunMeta, Option<SentryHandoff>)> {
+fn load_bootstrap(path: &std::path::Path) -> Result<(PathBuf, RunMeta, Option<String>)> {
let bytes = fs_err::read(path).into_diagnostic()?;
if let Err(e) = fs_err::remove_file(path) {
// Don't abort — the bytes are already loaded and the server
@@ -409,7 +412,7 @@ fn load_bootstrap(path: &std::path::Path) -> Result<(PathBuf, RunMeta, Option<Se
);
}
let bootstrap: Bootstrap = serde_json::from_slice(&bytes).into_diagnostic()?;
- Ok((bootstrap.git_dir, bootstrap.meta, bootstrap.sentry))
+ Ok((bootstrap.git_dir, bootstrap.meta, bootstrap.sentry_trace_id))
}
fn run_pipeline(
@@ -616,17 +619,17 @@ mod tests {
pushed_at: jiff::Timestamp::now(),
},
git_dir: PathBuf::from("/tmp/repo.git"),
- sentry: None,
+ sentry_trace_id: None,
};
fs_err::write(&path, serde_json::to_vec(&bootstrap).unwrap()).expect("write");
- let (git_dir, meta, sentry) = load_bootstrap(&path).expect("load");
+ let (git_dir, meta, sentry_trace_id) = load_bootstrap(&path).expect("load");
assert!(
!path.exists(),
"bootstrap file should be removed after read"
);
assert_eq!(git_dir, PathBuf::from("/tmp/repo.git"));
assert_eq!(meta.r#ref, "HEAD");
- assert!(sentry.is_none());
+ assert!(sentry_trace_id.is_none());
}
}
diff --git a/quire-core/Cargo.toml b/quire-core/Cargo.toml
index 5d63cdf..9c9763e 100644
--- a/quire-core/Cargo.toml
+++ b/quire-core/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
-facet = "*"
+facet = { workspace = true }
fs-err = { workspace = true }
jiff = { workspace = true }
miette = { workspace = true }
diff --git a/quire-core/src/ci/bootstrap.rs b/quire-core/src/ci/bootstrap.rs
index f8bc818..6e43157 100644
--- a/quire-core/src/ci/bootstrap.rs
+++ b/quire-core/src/ci/bootstrap.rs
@@ -28,20 +28,10 @@ use crate::ci::run::RunMeta;
pub struct Bootstrap {
pub meta: RunMeta,
pub git_dir: PathBuf,
- /// Sentry handoff, present only when the orchestrator's global
- /// config sets a DSN. Carries the matching trace id so both
- /// sides' events land on the same trace.
+ /// Sentry trace id for the orchestrator's span, present only when
+ /// the global config sets a DSN. Allows quire-ci to attach its
+ /// events to the same trace. The DSN itself travels via the
+ /// `QUIRE__SENTRY_DSN` environment variable.
#[serde(default, skip_serializing_if = "Option::is_none")]
- pub sentry: Option<SentryHandoff>,
-}
-
-/// What quire-ci needs to mirror the orchestrator's Sentry trace.
-///
-/// `trace_id` is the hex form of [`sentry::protocol::TraceId`]; kept
-/// as a string here so `quire-core` doesn't grow a `sentry` dep.
-/// The DSN itself travels via the `QUIRE__SENTRY_DSN` environment
-/// variable, not this struct.
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct SentryHandoff {
- pub trace_id: String,
+ pub sentry_trace_id: Option<String>,
}
diff --git a/quire-server/src/ci/mod.rs b/quire-server/src/ci/mod.rs
index 67249fb..ac8a6a5 100644
--- a/quire-server/src/ci/mod.rs
+++ b/quire-server/src/ci/mod.rs
@@ -185,12 +185,7 @@ fn run_ref(
span_id: sentry::protocol::SpanId,
) {
let transport = new_transport(ctx.transport_mode, ctx.port);
- let sentry_handoff =
- ctx.sentry_dsn
- .as_ref()
- .map(|_| quire_core::ci::bootstrap::SentryHandoff {
- trace_id: trace_id.to_string(),
- });
+ let sentry_trace_id = ctx.sentry_dsn.as_ref().map(|_| trace_id.to_string());
sentry::with_scope(
|scope| {
scope.set_context(
@@ -209,7 +204,7 @@ fn run_ref(
pushed_at,
push_ref,
&transport,
- sentry_handoff.as_ref(),
+ sentry_trace_id.as_deref(),
ctx.sentry_dsn.as_deref(),
) {
tracing::error!(
@@ -229,7 +224,7 @@ fn run_ref_inner(
pushed_at: jiff::Timestamp,
push_ref: &PushRef,
transport: &Transport,
- sentry: Option<&quire_core::ci::bootstrap::SentryHandoff>,
+ sentry_trace_id: Option<&str>,
sentry_dsn: Option<&str>,
) -> error::Result<()> {
let ci = ctx.repo.ci();
@@ -263,7 +258,7 @@ fn run_ref_inner(
&ctx.repo.path(),
&workspace,
&meta,
- sentry,
+ sentry_trace_id,
sentry_dsn,
Some(transport),
)?;
diff --git a/quire-server/src/ci/run.rs b/quire-server/src/ci/run.rs
index 2857386..9c23569 100644
--- a/quire-server/src/ci/run.rs
+++ b/quire-server/src/ci/run.rs
@@ -306,7 +306,7 @@ impl Run {
git_dir: &Path,
workspace: &Path,
meta: &RunMeta,
- sentry: Option<&quire_core::ci::bootstrap::SentryHandoff>,
+ sentry_trace_id: Option<&str>,
sentry_dsn: Option<&str>,
transport: Option<&Transport>,
) -> Result<()> {
@@ -321,7 +321,7 @@ impl Run {
let log = fs_err::File::create(&log_path)?.into_parts().0;
let log_clone = log.try_clone()?;
- write_bootstrap(&bootstrap_path, git_dir, meta, sentry)?;
+ write_bootstrap(&bootstrap_path, git_dir, meta, sentry_trace_id)?;
tracing::info!(
run_id = %self.id,
@@ -625,14 +625,14 @@ fn write_bootstrap(
path: &Path,
git_dir: &Path,
meta: &RunMeta,
- sentry: Option<&quire_core::ci::bootstrap::SentryHandoff>,
+ sentry_trace_id: Option<&str>,
) -> Result<()> {
use quire_core::ci::bootstrap::Bootstrap;
let bootstrap = Bootstrap {
meta: meta.clone(),
git_dir: git_dir.to_path_buf(),
- sentry: sentry.cloned(),
+ sentry_trace_id: sentry_trace_id.map(Into::into),
};
let json = serde_json::to_vec_pretty(&bootstrap).map_err(std::io::Error::other)?;