Address review: route fix, RepoConfig placement, push_to_mirror cleanup
- api.rs: revert {*name} wildcard back to {name} (no longer needed)
- quire/mod.rs: move RepoConfig / RepoGithubConfig to after impl Repo
  so they sit next to the type they belong to
- mirror.rs: push_to_mirror takes token: &str (always required) and
  returns Result<()>; caller logs the error; token absence is a per-ref
  warning rather than a fatal error

https://claude.ai/code/session_01MtUMXi7Z3GCDWQFY8puWpu
change
commit 5d8a0f5a26ac01d6f7b6fb7445c3b0c4adc6b621
author Claude <noreply@anthropic.com>
date
parent effd78c1
diff --git a/quire-server/src/mirror.rs b/quire-server/src/mirror.rs
index 6ead68f..e1b52f7 100644
--- a/quire-server/src/mirror.rs
+++ b/quire-server/src/mirror.rs
@@ -48,12 +48,22 @@ pub fn trigger(quire: &Quire, event: &PushEvent) -> crate::Result<()> {
             continue;
         };
 
-        push_to_mirror(
-            &repo,
-            &push_ref.ref_name,
-            &mirror_url,
-            mirror_token.as_deref(),
-        );
+        let Some(token) = mirror_token.as_deref() else {
+            tracing::warn!(
+                ref_name = %push_ref.ref_name,
+                "mirror: mirror-token not configured, skipping ref",
+            );
+            continue;
+        };
+
+        if let Err(e) = push_to_mirror(&repo, &push_ref.ref_name, &mirror_url, token) {
+            tracing::error!(
+                ref_name = %push_ref.ref_name,
+                mirror_url,
+                error = &e as &(dyn std::error::Error + 'static),
+                "mirror: push failed",
+            );
+        }
     }
 
     Ok(())
@@ -63,45 +73,31 @@ fn push_to_mirror(
     repo: &crate::quire::Repo,
     ref_name: &str,
     mirror_url: &str,
-    token: Option<&str>,
-) {
+    token: &str,
+) -> crate::Result<()> {
     // Force-push the ref to the mirror. The `+` prefix allows rewrites.
     let refspec = format!("+{ref_name}:{ref_name}");
-    let mut cmd = repo.git(&["push", "--porcelain", mirror_url, &refspec]);
 
     // Pass the auth token via git config env vars so it never appears in argv.
-    if let Some(token) = token {
-        cmd.env("GIT_CONFIG_COUNT", "1")
-            .env("GIT_CONFIG_KEY_0", "http.extraHeader")
-            .env(
-                "GIT_CONFIG_VALUE_0",
-                format!("Authorization: Bearer {token}"),
-            );
-    }
-
-    match cmd
+    let out = repo
+        .git(&["push", "--porcelain", mirror_url, &refspec])
+        .env("GIT_CONFIG_COUNT", "1")
+        .env("GIT_CONFIG_KEY_0", "http.extraHeader")
+        .env(
+            "GIT_CONFIG_VALUE_0",
+            format!("Authorization: Bearer {token}"),
+        )
         .stdout(std::process::Stdio::piped())
         .stderr(std::process::Stdio::piped())
-        .output()
-    {
-        Ok(out) if out.status.success() => {
-            tracing::info!(ref_name, mirror_url, "mirror: push succeeded");
-        }
-        Ok(out) => {
-            tracing::error!(
-                ref_name,
-                mirror_url,
-                stderr = %String::from_utf8_lossy(&out.stderr),
-                "mirror: push failed",
-            );
-        }
-        Err(e) => {
-            tracing::error!(
-                ref_name,
-                mirror_url,
-                error = %e,
-                "mirror: failed to run git push",
-            );
-        }
+        .output()?;
+
+    if !out.status.success() {
+        let stderr = String::from_utf8_lossy(&out.stderr);
+        return Err(crate::Error::Io(std::io::Error::other(format!(
+            "git push to {mirror_url} failed: {stderr}"
+        ))));
     }
+
+    tracing::info!(ref_name, mirror_url, "mirror: push succeeded");
+    Ok(())
 }
diff --git a/quire-server/src/quire/mod.rs b/quire-server/src/quire/mod.rs
index a9a11ef..0907f56 100644
--- a/quire-server/src/quire/mod.rs
+++ b/quire-server/src/quire/mod.rs
@@ -51,22 +51,6 @@ fn default_port() -> u16 {
     3000
 }
 
-/// Per-repo CI configuration parsed from `.quire/config.fnl`.
-#[derive(serde::Deserialize, Debug, Default, Clone)]
-#[serde(default, rename_all = "kebab-case")]
-pub struct RepoConfig {
-    pub github: RepoGithubConfig,
-}
-
-/// Per-repo GitHub configuration.
-#[derive(serde::Deserialize, Debug, Default, Clone)]
-#[serde(default, rename_all = "kebab-case")]
-pub struct RepoGithubConfig {
-    /// Remote URL to mirror every pushed ref to.
-    /// E.g. `"https://github.com/user/repo.git"`.
-    pub mirror: Option<String>,
-}
-
 #[derive(serde::Deserialize, Debug, Default)]
 pub struct CiConfig {
     /// How the orchestrator dispatches CI runs. Defaults to shelling
@@ -211,6 +195,22 @@ impl Repo {
     }
 }
 
+/// Per-repo CI configuration parsed from `.quire/config.fnl`.
+#[derive(serde::Deserialize, Debug, Default, Clone)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct RepoConfig {
+    pub github: RepoGithubConfig,
+}
+
+/// Per-repo GitHub configuration.
+#[derive(serde::Deserialize, Debug, Default, Clone)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct RepoGithubConfig {
+    /// Remote URL to mirror every pushed ref to.
+    /// E.g. `"https://github.com/user/repo.git"`.
+    pub mirror: Option<String>,
+}
+
 /// Application runtime context.
 ///
 /// Carries configuration and provides resolved paths to repositories.
diff --git a/quire-server/src/quire/web/api.rs b/quire-server/src/quire/web/api.rs
index 5ef2670..ce24d4b 100644
--- a/quire-server/src/quire/web/api.rs
+++ b/quire-server/src/quire/web/api.rs
@@ -28,7 +28,7 @@ use crate::Quire;
 pub fn router(quire: Quire) -> axum::Router {
     let run_routes = axum::Router::new()
         .route("/bootstrap", axum::routing::get(get_bootstrap))
-        .route("/secrets/{*name}", axum::routing::get(get_secret))
+        .route("/secrets/{name}", axum::routing::get(get_secret))
         .layer(axum::middleware::from_fn_with_state(
             quire.clone(),
             verify_run_token,
@@ -204,7 +204,6 @@ async fn get_bootstrap(
 /// `GET /api/run/secrets/:name`
 ///
 /// Returns the plain-text value of a named secret from the global config.
-/// Supports slash-separated names via the `{*name}` wildcard route.
 /// Auth is handled by [`verify_run_token`] middleware.
 /// Returns 404 if the secret is not declared in config.
 #[derive(serde::Deserialize)]