From b2c9acdaa60597bda8f6697b6eab58876098fb62 Mon Sep 17 00:00:00 2001 From: clawbot Date: Mon, 24 Aug 2026 06:25:08 +0200 Subject: [PATCH] Correct four documentation claims ahead of the 1.0.0 tag --- README.md | 25 ++++++++++++++++++++++--- TODO.md | 29 +++++++++++++++++++---------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8483fdc..c66e5ca 100644 --- a/README.md +++ b/README.md @@ -1234,6 +1234,16 @@ webhooker solves this by acting as a durable intermediary: backoff. Every delivery attempt is logged with status codes, response bodies, and timing. + **That guarantee is at-least-once, not exactly-once.** When a send + reaches its target but the write recording that outcome fails, the + delivery is deliberately left in a recoverable state rather than + marked done — losing a delivery is the worse failure — so the + pending sweep picks it up about fifteen minutes later, or the next + restart does, and the target receives a payload it already got. + webhooker adds no delivery identifier of its own to an outbound + request, so **make your receiver idempotent** against whatever the + payload itself carries. + 3. **Observability** — Full request/response logging for every webhook received and every delivery attempted. Prometheus metrics expose volume, latency, and error rates. The web UI provides real-time @@ -2616,12 +2626,15 @@ abuse limit later; they are tracked as future work. | `POST` | `/source/{id}/edit` | Edit webhook submission | | `POST` | `/source/{id}/delete` | Delete webhook | | `GET` | `/source/{id}/logs` | Webhook event logs | +| `GET` | `/source/{id}/logs/{eventID}/body` | Download an event's full stored body. The log page renders each body only up to its cap, so this is the only route that serves a whole one; it is offered wherever a body is shown truncated | | `POST` | `/source/{id}/deliveries/{deliveryID}/replay` | Replay a finished delivery: creates a new delivery for the same event against the target's current configuration (30 per minute per bucket, then `429`) | | `POST` | `/source/{id}/events/{eventID}/resubmit` | Resubmit a stored event: creates a new event copying it and fans that out to every currently active target (30 per minute per bucket, then `429`) | | `POST` | `/source/{id}/entrypoints` | Add entrypoint to webhook | | `POST` | `/source/{id}/entrypoints/{entrypointID}/delete` | Delete an entrypoint | | `POST` | `/source/{id}/entrypoints/{entrypointID}/toggle` | Enable or disable an entrypoint | | `POST` | `/source/{id}/targets` | Add target to webhook | +| `GET` | `/source/{id}/targets/{targetID}/edit` | Edit target form. The one page that renders a target's destination URL and header values in full, rather than masked | +| `POST` | `/source/{id}/targets/{targetID}/edit` | Edit target submission | | `POST` | `/source/{id}/targets/{targetID}/delete` | Delete a target | | `POST` | `/source/{id}/targets/{targetID}/toggle` | Enable or disable a target | @@ -2660,6 +2673,8 @@ webhooker/ ├── internal/ │ ├── banner/ │ │ └── banner.go # Ruled block for the one credential shown in the clear +│ ├── ciscript/ +│ │ └── doc.go # Tests for the CI shell scripts in script/; no runtime code │ ├── resetpw/ │ │ └── resetpw.go # `webhooker resetpw`: set an account's password, stopped deployments only │ ├── config/ @@ -2729,13 +2744,17 @@ webhooker/ │ │ ├── ratelimit.go # Per-IP rate limiting middleware (go-chi/httprate) │ │ ├── loginguard.go # Login failure counters and the Argon2id verification semaphore │ │ └── testing.go # NewForTest: Middleware without the fx lifecycle +│ ├── reqtls/ +│ │ └── reqtls.go # IsTLS: the one TLS predicate, r.TLS or X-Forwarded-Proto │ ├── server/ │ │ ├── server.go # Server struct, fx lifecycle, signal handling │ │ ├── http.go # HTTP server setup with timeouts │ │ └── routes.go # All route definitions -│ └── session/ -│ ├── session.go # Cookie-based session management -│ └── testing.go # NewForTest: Session without the fx lifecycle +│ ├── session/ +│ │ ├── session.go # Cookie-based session management +│ │ └── testing.go # NewForTest: Session without the fx lifecycle +│ └── versionscript/ +│ └── doc.go # Tests for script/version and the build files that use it ├── static/ │ ├── static.go # //go:embed directive │ ├── css/input.css # Tailwind input, source for tailwind.css (make css) diff --git a/TODO.md b/TODO.md index 7b51192..5dec10d 100644 --- a/TODO.md +++ b/TODO.md @@ -25,11 +25,20 @@ file records where the project is, not what is in flight: a sentence whose truth depends on a branch being unmerged is wrong the moment it merges, and this file has been wrong that way before. -The tag is held on a durability defect -(https://git.eeqj.de/sneak/webhooker/issues/256): a concurrent reader -of a per-webhook event database strands delivered webhooks at -`pending`, and the next restart re-delivers them. That issue gates -`v1.0.0`, and is where the fix's own state is tracked. +The durability defect that held the tag has landed +(https://git.eeqj.de/sneak/webhooker/issues/256, commit `8d64259`). +Every SQLite handle opens with WAL journaling and a busy timeout, a +bookkeeping write that fails leaves its delivery in a recoverable +state rather than a lying one, and recovery skips a delivery that +already has a successful result row. Final pre-tag verification +exercised it and confirmed it holds. Whatever the milestone still +shows open is what remains before `v1.0.0`. + +Delivery is at-least-once by design, not by accident: a send whose +result row does not land is attempted again, so a receiver can see a +duplicate. That is deliberate — the alternative is a silent lost +delivery — and the README says so under Rationale. It is not a defect +to re-file. One caveat on reading a green check: a docs-only commit deliberately replays from the layer cache @@ -39,11 +48,11 @@ commit invalidates the `COPY` layer and genuinely executes. # Next Step -Land https://git.eeqj.de/sneak/webhooker/issues/256, then clear the -rest of the open 1.0.0 milestone and tag `v1.0.0`. Merging `next` into -`main` is a separate act from tagging and waits on neither of those: -`next` is kept mergeable at all times, which is the point of the -branch. +Clear the rest of the open 1.0.0 milestone +(https://git.eeqj.de/sneak/webhooker/milestone/9) and tag `v1.0.0`. +Merging `next` into `main` is a separate act from tagging and waits on +neither of those: `next` is kept mergeable at all times, which is the +point of the branch. # Completed Steps