Watch
0
0
Fork
You've already forked hyperhive
0

swarm: the notification router tells a PR's author when CI on it fails or is cancelled

This commit is contained in:
damocles 2026-11-06 21:25:08 +01:00 • committed by mara
commit c8eaa93268
3 changed files with 490 additions and 60 deletions

View file

@ -1892,6 +1892,14 @@ fn hook_registration(kind: DeliveryKind) -> (&'static [&'static str], HookScope<
],
HookScope::Instance,
),
// Instance-wide too: the outcome of a CI run, which the router tells a
// PR's author about. A hook of its own, because Forgejo's hook edit
// never sets `workflow_run_*` events: a hook can only get them when it
// is created.
DeliveryKind::WorkflowRun => (
&["workflow_run_failure", "workflow_run_cancelled"],
HookScope::Instance,
),
}
}
@ -2244,10 +2252,15 @@ mod tests {
"pull_request_review_comment",
]
);
assert_eq!(
events(DeliveryKind::WorkflowRun),
["workflow_run_failure", "workflow_run_cancelled"]
);
// The events a stored flow's source may name are the ones the hooks
// deliver to the router.
let mut routed = events(DeliveryKind::Issues);
routed.extend(events(DeliveryKind::PullRequest));
routed.extend(events(DeliveryKind::WorkflowRun));
routed.sort_unstable();
let mut sources = swarm_wire::notify_flows::SOURCE_EVENTS
.iter()

View file

@ -303,7 +303,7 @@ mod tests {
let Json(view) = get_flows(State(state.clone())).await.expect("get");
assert_eq!(view.revision, 0);
assert!(view.disabled_builtins.is_empty() && view.flows.is_empty());
assert_eq!(view.builtins.len(), 4);
assert_eq!(view.builtins.len(), notify_flows::builtins().len());
assert!(view.builtins.iter().any(|f| f.id == "head-classifier"));
let json = serde_json::to_value(&view).expect("serializes");
assert_eq!(json["schema"]["limits"]["max_nodes"], 64);
@ -331,7 +331,11 @@ mod tests {
.expect("read")
.expect("stored");
let doc = Document::decode(&bytes).expect("the router reads it");
assert_eq!(doc.active().0.len(), 4, "3 built-ins and the custom flow");
assert_eq!(
doc.active().0.len(),
notify_flows::builtins().len(),
"the built-ins less the switched-off one, and the custom flow"
);
// A refused save changes nothing: an orphan node, and a built-in that does not exist.
let mut orphan = custom("orphan");

View file

@ -420,6 +420,13 @@ pub(super) enum DeliveryKind {
/// the instance, kept the same way.
#[strum(serialize = "pull-request")]
PullRequest,
/// Every finished CI run that failed or was cancelled, on every repo in
/// the instance, kept the same way. Its own hook: Forgejo's hook edit
/// ignores the `workflow_run_*` events, so they can only be set when a
/// hook is created, and a `pull-request` hook registered earlier could not
/// gain them.
#[strum(serialize = "workflow-run")]
WorkflowRun,
}
/// The route prefix a registered `target_url` must point at.
@ -435,13 +442,14 @@ impl DeliveryKind {
/// Every kind, for the registration sweep. An array rather than a
/// hand-written list at the call site, so adding a kind cannot leave one
/// hook unregistered.
pub(super) const ALL: [Self; 6] = [
pub(super) const ALL: [Self; 7] = [
Self::Knowledge,
Self::ConfigPr,
Self::VcsActivity,
Self::ReviewRequest,
Self::Issues,
Self::PullRequest,
Self::WorkflowRun,
];
/// The `target_url` to register with Forgejo for this kind, given the
@ -549,8 +557,8 @@ pub(super) fn verify(
/// comment for why both this and a periodic poll write the same cache), and a
/// merge queues a deploy of the merged commit on the agent's hive. A
/// `ReviewRequest` delivery naming the review classifier's account starts a
/// review run in the background. An `Issues` or `PullRequest` delivery is
/// kept in the forge events stream for the notification router
/// review run in the background. An `Issues`, `PullRequest` or `WorkflowRun`
/// delivery is kept in the forge events stream for the notification router
/// (`crate::forge_events`).
///
/// Returns 200 on an accepted delivery so Forgejo does not retry. A refused
@ -564,7 +572,8 @@ pub(super) fn verify(
params(
("kind" = String, Path, description = "which swarm-wide hook this delivery arrived on: \
`knowledge`, `config-pr`, `vcs-activity`, \
`review-request`, `issues` or `pull-request`")
`review-request`, `issues`, `pull-request` or \
`workflow-run`")
),
request_body(
content = String,
@ -637,7 +646,7 @@ pub(super) async fn post_webhook_forge(
}
}
DeliveryKind::ReviewRequest => crate::review_classifier::review::on_delivery(&state, &body),
DeliveryKind::Issues | DeliveryKind::PullRequest => {
DeliveryKind::Issues | DeliveryKind::PullRequest | DeliveryKind::WorkflowRun => {
if let Err(not_kept) = crate::forge_events::publish(&state, &headers, body).await {
tracing::warn!(kind = <&str>::from(kind), reason = ?not_kept, "webhook: forge event not kept");
return (StatusCode::SERVICE_UNAVAILABLE, "forge event not kept").into_response();