refactor: merge retry target type into http (max_retries=0 = fire-and-forget)
All checks were successful
check / check (push) Successful in 1m46s

This commit is contained in:
clawbot
2026-03-01 23:51:55 -08:00
parent 4dd4dfa5eb
commit 25e27cc57f
7 changed files with 150 additions and 105 deletions

View File

@@ -291,20 +291,23 @@ events should be forwarded.
| `id` | UUID | Primary key |
| `webhook_id` | UUID | Foreign key → Webhook |
| `name` | string | Human-readable name |
| `type` | TargetType | One of: `http`, `retry`, `database`, `log` |
| `type` | TargetType | One of: `http`, `database`, `log` |
| `active` | boolean | Whether deliveries are enabled (default: true) |
| `config` | JSON text | Type-specific configuration |
| `max_retries` | integer | Maximum retry attempts (for retry targets) |
| `max_queue_size` | integer | Maximum queued deliveries (for retry targets) |
| `max_retries` | integer | Maximum retry attempts for HTTP targets (0 = fire-and-forget, >0 = retries with backoff) |
| `max_queue_size` | integer | Maximum queued deliveries (for HTTP targets with retries) |
**Relations:** Belongs to Webhook. Has many Deliveries.
**Target types:**
- **`http`** — Forward the event as an HTTP POST to a configured URL.
Fire-and-forget: a single attempt with no retries.
- **`retry`** — Forward the event via HTTP POST with automatic retry on
failure. Uses exponential backoff up to `max_retries` attempts.
Behavior depends on `max_retries`: when `max_retries` is 0 (the
default), the target operates in fire-and-forget mode — a single
attempt with no retries and no circuit breaker. When `max_retries` is
greater than 0, failed deliveries are retried with exponential backoff
up to `max_retries` attempts, protected by a per-target circuit
breaker.
- **`database`** — Confirm the event is stored in the webhook's
per-webhook database (no external delivery). Since events are always
written to the per-webhook DB on ingestion, this target marks delivery
@@ -495,10 +498,12 @@ External Service
┌── bounded worker pool (N workers) ──┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ HTTP Target│ │Retry Target│ │ Log Target │
(1 attempt)│ │ (backoff + │ │ (stdout) │
└────────────┘ │ circuit │ └────────────┘
│ breaker)
│ HTTP Target│ │ HTTP Target│ │ Log Target │
(max_retries│ │(max_retries│ │ (stdout) │
│ == 0) │ │ > 0, │ └────────────┘
│ fire+forget│ │ backoff +
└────────────┘ │ circuit │
│ breaker) │
└────────────┘
```
@@ -553,9 +558,9 @@ This means:
durable fallback that ensures no retry is permanently lost, even under
extreme backpressure.
### Circuit Breaker (Retry Targets)
### Circuit Breaker (HTTP Targets with Retries)
Retry targets are protected by a **per-target circuit breaker** that
HTTP targets with `max_retries` > 0 are protected by a **per-target circuit breaker** that
prevents hammering a down target with repeated failed delivery attempts.
The circuit breaker is in-memory only and resets on restart (which is
fine — startup recovery rescans the database anyway).
@@ -594,9 +599,10 @@ fine — startup recovery rescans the database anyway).
- **Failure threshold:** 5 consecutive failures before opening
- **Cooldown:** 30 seconds in open state before probing
**Scope:** Circuit breakers only apply to **retry** target types. HTTP
targets (fire-and-forget), database targets (local operations), and log
targets (stdout) do not use circuit breakers.
**Scope:** Circuit breakers only apply to **HTTP targets with
`max_retries` > 0**. Fire-and-forget HTTP targets (`max_retries` == 0),
database targets (local operations), and log targets (stdout) do not use
circuit breakers.
When a circuit is open and a new delivery arrives, the engine marks the
delivery as `retrying` and schedules a retry timer for after the
@@ -704,7 +710,7 @@ webhooker/
│ │ └── globals.go # Build-time variables (appname, version, arch)
│ ├── delivery/
│ │ ├── engine.go # Event-driven delivery engine (channel + timer based)
│ │ └── circuit_breaker.go # Per-target circuit breaker for retry targets
│ │ └── circuit_breaker.go # Per-target circuit breaker for HTTP targets with retries
│ ├── handlers/
│ │ ├── handlers.go # Base handler struct, JSON helpers, template rendering
│ │ ├── auth.go # Login, logout handlers
@@ -838,8 +844,8 @@ linted, tested, and compiled.
### Completed: Core Webhook Engine (Phase 2 of MVP)
- [x] Implement webhook reception and event storage at `/webhook/{uuid}`
- [x] Build event processing and target delivery engine
- [x] Implement HTTP target type (fire-and-forget POST)
- [x] Implement retry target type (exponential backoff)
- [x] Implement HTTP target type (fire-and-forget with max_retries=0,
retries with exponential backoff when max_retries>0)
- [x] Implement database target type (store events in per-webhook DB)
- [x] Implement log target type (console output)
- [x] Webhook management pages (list, create, edit, delete)
@@ -861,9 +867,9 @@ linted, tested, and compiled.
(events are already in the per-webhook DB)
- [x] Parallel fan-out: all targets for an event are delivered via
the bounded worker pool (no goroutine-per-target)
- [x] Circuit breaker for retry targets: tracks consecutive failures
per target, opens after 5 failures (30s cooldown), half-open
probe to test recovery
- [x] Circuit breaker for HTTP targets with retries: tracks consecutive
failures per target, opens after 5 failures (30s cooldown),
half-open probe to test recovery
### Remaining: Core Features
- [ ] Per-webhook rate limiting in the receiver handler