mirror: return concrete Result from trigger, convert to diagnostic at callsite
trigger now returns Result<(), impl Diagnostic + Send + Sync> instead of
miette::Result<()>, keeping MirrorErrors fully private. The callsite in
server.rs converts to miette::Report via the blanket From impl.

Also adds MirrorError::Secret for secret::Error and MirrorError::Quire
for miette::Report (from quire.repo() which returns miette::Result).

https://claude.ai/code/session_01MtUMXi7Z3GCDWQFY8puWpu
change
commit fe73ca8ea295e37b88ecd58bed4d26b16d74a08a
author Claude <noreply@anthropic.com>
date
parent 0da0d6aa
diff --git a/quire-server/src/bin/quire/server.rs b/quire-server/src/bin/quire/server.rs
index b39b434..cc2c306 100644
--- a/quire-server/src/bin/quire/server.rs
+++ b/quire-server/src/bin/quire/server.rs
@@ -158,6 +158,6 @@ async fn handle_event_connection(mut stream: tokio::net::UnixStream, quire: Quir
 
     ci::trigger(&quire, &event);
     if let Err(e) = mirror::trigger(&quire, &event) {
-        tracing::error!(repo = %event.repo, error = %e, "mirror failed");
+        tracing::error!(repo = %event.repo, error = %miette::Report::from(e), "mirror failed");
     }
 }
diff --git a/quire-server/src/mirror.rs b/quire-server/src/mirror.rs
index 23837af..bd128c4 100644
--- a/quire-server/src/mirror.rs
+++ b/quire-server/src/mirror.rs
@@ -2,20 +2,25 @@
 //!
 //! Triggered from the push event handler, independent of CI.
 
-use miette::{Diagnostic, IntoDiagnostic as _};
 use quire_core::event::{PushEvent, PushRef};
 use thiserror::Error;
 
 use crate::quire::Quire;
 
-#[derive(Debug, Error, Diagnostic)]
+#[derive(Debug, Error, miette::Diagnostic)]
 #[error("mirror: {} ref(s) failed", errors.len())]
 struct MirrorErrors {
     #[related]
     errors: Vec<MirrorError>,
 }
 
-#[derive(Debug, Error, Diagnostic)]
+impl From<MirrorError> for MirrorErrors {
+    fn from(e: MirrorError) -> Self {
+        MirrorErrors { errors: vec![e] }
+    }
+}
+
+#[derive(Debug, Error, miette::Diagnostic)]
 enum MirrorError {
     #[error("repo not found on disk: {0}")]
     RepoNotFound(String),
@@ -29,8 +34,15 @@ enum MirrorError {
     #[error(transparent)]
     App(#[from] crate::Error),
 
+    #[error(transparent)]
+    Secret(#[from] quire_core::secret::Error),
+
     #[error(transparent)]
     Io(#[from] std::io::Error),
+
+    #[error("{0}")]
+    #[diagnostic(transparent)]
+    Quire(miette::Report),
 }
 
 /// Mirror updated refs to a configured remote.
@@ -38,19 +50,22 @@ enum MirrorError {
 /// Reads `github.mirror-token` from global config for auth. For each updated
 /// ref, reads `.quire/config.fnl` at the new SHA to obtain the `github.mirror`
 /// URL. Skips repos with no mirror URL configured.
-pub fn trigger(quire: &Quire, event: &PushEvent) -> miette::Result<()> {
-    let repo = quire.repo(&event.repo)?;
+pub fn trigger(
+    quire: &Quire,
+    event: &PushEvent,
+) -> Result<(), impl miette::Diagnostic + Send + Sync + 'static> {
+    let repo = quire.repo(&event.repo).map_err(MirrorError::Quire)?;
     if !repo.exists() {
         return Err(MirrorError::RepoNotFound(event.repo.clone()).into());
     }
 
-    let config = quire.global_config().into_diagnostic()?;
+    let config = quire.global_config().map_err(MirrorError::App)?;
     let mirror_token = config
         .github
         .mirror_token
         .map(|s| s.reveal().map(str::to_owned))
         .transpose()
-        .into_diagnostic()?;
+        .map_err(MirrorError::Secret)?;
 
     let errors: Vec<MirrorError> = event
         .updated_refs()
@@ -61,7 +76,7 @@ pub fn trigger(quire: &Quire, event: &PushEvent) -> miette::Result<()> {
     if errors.is_empty() {
         Ok(())
     } else {
-        Err(MirrorErrors { errors }.into())
+        Err(MirrorErrors { errors })
     }
 }