Add event-driven tasks via Gitea webhooks
Some checks failed
Deploy Production / deploy (push) Failing after 35s
CI / lint-and-test (push) Successful in 33s
CI / build (push) Has been cancelled

Webhook endpoint at /api/webhooks/gitea receives Gitea status events,
matches them against configurable event triggers with conditions
(event type, repo glob, state, context), renders task templates with
{{variable}} substitution, and creates harness tasks automatically.

Includes circuit breaker: after N consecutive task failures from the
same trigger (default 3), the trigger auto-disables. Re-enable
manually via PATCH /api/event-triggers/:id.

New tables: harness_event_triggers (rules + circuit breaker state),
harness_event_log (audit trail + dedup via X-Gitea-Delivery).
This commit is contained in:
Julia McGhee
2026-03-21 21:15:15 +00:00
parent ccebbc4015
commit eeb87018d7
19 changed files with 2368 additions and 35 deletions

View File

@@ -0,0 +1,34 @@
// Match webhook events against event trigger conditions.
import { getEnabledEventTriggers, type EventTrigger } from "./event-store";
import { type ParsedEvent } from "./template";
function escapeRegex(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function globMatch(pattern: string, value: string): boolean {
const regex = new RegExp(
"^" + pattern.split("*").map(escapeRegex).join(".*") + "$",
);
return regex.test(value);
}
export function matchesTrigger(trigger: EventTrigger, event: ParsedEvent): boolean {
if (!trigger.enabled) return false;
if (trigger.eventType !== "*" && trigger.eventType !== event.eventType) return false;
if (trigger.repoFilter && !globMatch(trigger.repoFilter, event.repo)) return false;
if (trigger.stateFilter && trigger.stateFilter !== event.state) return false;
if (trigger.contextFilter && !event.context.includes(trigger.contextFilter)) return false;
return true;
}
export async function findMatchingTriggers(event: ParsedEvent): Promise<EventTrigger[]> {
const triggers = await getEnabledEventTriggers();
return triggers.filter(t => matchesTrigger(t, event));
}