diff --git a/crates/api/src/middleware.rs b/crates/api/src/middleware.rs
index 382733f..bc5e756 100644
--- a/crates/api/src/middleware.rs
+++ b/crates/api/src/middleware.rs
@@ -1,4 +1,5 @@
// API key authentication middleware for admin routes.
+// + Request ID middleware for trace propagation across services.
use axum::{
body::Body,
extract::State,
@@ -11,6 +12,31 @@ use serde_json::json;
use crate::state::AppState;
+
+// Request ID middleware: reads X-Request-Id header or generates a UUIDv4,
+// injects it into response headers, and attaches it to the tracing span.
+pub async fn request_id(
+ req: Request
,
+ next: Next,
+) -> Response {
+ let request_id = req
+ .headers()
+ .get("x-request-id")
+ .and_then(|v| v.to_str().ok())
+ .map(str::to_string)
+ .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
+
+ tracing::Span::current().record("request_id", &request_id);
+
+ let mut response = next.run(req).await;
+ response.headers_mut().insert(
+ "x-request-id",
+ request_id.parse().unwrap(),
+ );
+ response
+}
+
+
pub async fn require_admin_key(
State(state): State,
req: Request,
diff --git a/crates/gateway/src/routes.rs b/crates/gateway/src/routes.rs
index 861394a..d67d749 100644
--- a/crates/gateway/src/routes.rs
+++ b/crates/gateway/src/routes.rs
@@ -20,11 +20,37 @@ use crate::attestation::{
use crate::state::AppState;
use crate::webhook::{parse_merge_event, verify_github_signature};
+// Request ID middleware for trace propagation across services.
+async fn request_id(req: Request, next: Next) -> Response {
+ use axum::body::Body;
+ use axum::http::Request;
+ use axum::middleware::Next;
+ use axum::response::Response;
+
+ let request_id = req
+ .headers()
+ .get("x-request-id")
+ .and_then(|v| v.to_str().ok())
+ .map(str::to_string)
+ .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
+
+ tracing::Span::current().record("request_id", &request_id);
+
+ let mut response = next.run(req).await;
+ response.headers_mut().insert(
+ "x-request-id",
+ request_id.parse().unwrap(),
+ );
+ response
+}
+
+
pub fn router(state: AppState) -> Router {
Router::new()
.route("/health", get(health))
.route("/ready", get(ready))
.route("/webhooks/github", post(github_webhook))
+ .layer(axum::middleware::from_fn(request_id))
.with_state(state)
}