Break dispatch_push into dispatch_ci and dispatch_mirror
Also extracts eval_and_validate as a helper for the CI eval path.

Assisted-by: GLM-5.1 via pi
change xxqlqvswzvkuuusmsyrsvqypvrtkrukt
commit cb489cbc3dcf1a4f3aaa593b1b7d3b480682a401
author Alpha Chen <alpha@kejadlen.dev>
date
parent wvulvmpl
diff --git a/src/event.rs b/src/event.rs
index ad2226b..b5ee1e3 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -29,88 +29,84 @@ pub async fn dispatch_push(quire: &crate::Quire, event: &PushEvent) {
         }
     };
 
-    // CI gating: check each updated ref for .quire/ci.fnl.
+    dispatch_ci(&repo, event);
+    dispatch_mirror(quire, repo, event).await;
+}
+
+/// Check each updated ref for .quire/ci.fnl, create runs, and eval + validate.
+fn dispatch_ci(repo: &crate::quire::Repo, event: &PushEvent) {
+    use crate::ci::{RunMeta, RunState, RunStateFile};
+
     for push_ref in &event.refs {
         // Skip deletions (all-zero new sha).
         if push_ref.new_sha == "0000000000000000000000000000000000000000" {
             continue;
         }
 
-        if repo.has_ci_fnl(&push_ref.new_sha) {
-            let meta = crate::ci::RunMeta {
-                sha: push_ref.new_sha.clone(),
-                r#ref: push_ref.r#ref.clone(),
-                pushed_at: event.pushed_at.clone(),
-            };
-
-            let runs = repo.runs();
-            match runs.create(&meta) {
-                Ok(mut run) => {
-                    tracing::info!(
-                        run_id = %run.id(),
-                        sha = %push_ref.new_sha,
-                        r#ref = %push_ref.r#ref,
-                        "created CI run"
-                    );
-
-                    // Transition to active, eval ci.fnl, validate.
-                    run.transition(crate::ci::RunState::Active)
-                        .unwrap_or_else(|e| {
-                            tracing::error!(run_id = %run.id(), %e, "failed to transition run to active");
-                        });
-
-                    let result = (|| -> crate::Result<()> {
-                        let source = repo.ci_fnl_source(&push_ref.new_sha)?;
-                        let fennel = crate::fennel::Fennel::new()?;
-                        let eval_result = crate::ci::eval_ci(
-                            &fennel,
-                            &source,
-                            &format!("{}:.quire/ci.fnl", &push_ref.new_sha),
-                        )?;
-                        crate::ci::validate(&eval_result.jobs)?;
-                        Ok(())
-                    })();
-
-                    match result {
-                        Ok(()) => {
-                            if let Err(e) = run.transition(crate::ci::RunState::Complete) {
-                                tracing::error!(
-                                    run_id = %run.id(),
-                                    %e,
-                                    "failed to transition run to complete"
-                                );
-                            }
-                        }
-                        Err(e) => {
-                            tracing::error!(
-                                run_id = %run.id(),
-                                %e,
-                                "CI evaluation failed"
-                            );
-                            if let Err(te) = run.transition(crate::ci::RunState::Failed) {
-                                tracing::error!(run_id = %run.id(), %te, "failed to transition run to failed");
-                            } else if let Err(we) = run.write_state(&crate::ci::RunStateFile {
-                                status: crate::ci::RunState::Failed,
-                                started_at: None,
-                                finished_at: Some(jiff::Zoned::now().to_string()),
-                            }) {
-                                tracing::error!(run_id = %run.id(), %we, "failed to write state for failed run");
-                            }
-                        }
-                    }
+        if !repo.has_ci_fnl(&push_ref.new_sha) {
+            continue;
+        }
+
+        let meta = RunMeta {
+            sha: push_ref.new_sha.clone(),
+            r#ref: push_ref.r#ref.clone(),
+            pushed_at: event.pushed_at.clone(),
+        };
+
+        let runs = repo.runs();
+        let mut run = match runs.create(&meta) {
+            Ok(r) => r,
+            Err(e) => {
+                tracing::error!(repo = %event.repo, %e, "failed to create CI run");
+                continue;
+            }
+        };
+
+        tracing::info!(
+            run_id = %run.id(),
+            sha = %push_ref.new_sha,
+            r#ref = %push_ref.r#ref,
+            "created CI run"
+        );
+
+        run.transition(RunState::Active).unwrap_or_else(|e| {
+            tracing::error!(run_id = %run.id(), %e, "failed to transition run to active");
+        });
+
+        let result = eval_and_validate(repo, &push_ref.new_sha);
+        match result {
+            Ok(()) => {
+                if let Err(e) = run.transition(RunState::Complete) {
+                    tracing::error!(run_id = %run.id(), %e, "failed to transition run to complete");
                 }
-                Err(e) => {
-                    tracing::error!(
-                        repo = %event.repo,
-                        %e,
-                        "failed to create CI run"
-                    );
+            }
+            Err(e) => {
+                tracing::error!(run_id = %run.id(), %e, "CI evaluation failed");
+                if let Err(te) = run.transition(RunState::Failed) {
+                    tracing::error!(run_id = %run.id(), %te, "failed to transition run to failed");
+                } else if let Err(we) = run.write_state(&RunStateFile {
+                    status: RunState::Failed,
+                    started_at: None,
+                    finished_at: Some(jiff::Zoned::now().to_string()),
+                }) {
+                    tracing::error!(run_id = %run.id(), %we, "failed to write state for failed run");
                 }
             }
         }
     }
+}
+
+/// Evaluate ci.fnl at a given SHA and validate the job graph.
+fn eval_and_validate(repo: &crate::quire::Repo, sha: &str) -> crate::Result<()> {
+    let source = repo.ci_fnl_source(sha)?;
+    let fennel = crate::fennel::Fennel::new()?;
+    let eval_result = crate::ci::eval_ci(&fennel, &source, &format!("{sha}:.quire/ci.fnl"))?;
+    crate::ci::validate(&eval_result.jobs)?;
+    Ok(())
+}
 
-    // Mirror push — proceeds regardless of CI.
+/// Push updated refs to the configured mirror.
+async fn dispatch_mirror(quire: &crate::Quire, repo: crate::quire::Repo, event: &PushEvent) {
     let config = match repo.config() {
         Ok(c) => c,
         Err(e) => {