v0.48.12

thevoiceguy/siphon-aiv0.48.12Aug 10, 2026by github-actions[bot]

Upgrading?

AI Summary

Maintenance release fixing webhook spool durability by removing the hardcoded 8-hour age limit.

Key Highlights

  • Fixed webhook/audit spool entries being deleted after 8 hours.
  • Introduced configurable `spool_max_age_secs` (default 72h) for all four spooling sinks.
  • Fixed metrics documentation for spool depth and deliveries.

New Features

  • Configurable spool maximum age limit.

Full Release Notes

### Fixed

- **Spooled webhook/audit deliveries are no longer destroyed after 8 hours** (issue #467). A delivery that exhausted its in-memory `retry_max` was persisted to `spool_dir` and re-attempted by the drain worker — but a hard-coded `MAX_DRAIN_ATTEMPTS = 100`, combined with the drain's capped backoff (`min(10·2^min(a,5), 300)`), meant the entry was deleted after **exactly 8.00 hours** of a receiver being unreachable. The limit was undocumented, unconfigurable, and expressed in a unit no operator could reason about without summing the backoff series by hand, while `DEPLOY.md` described the spool as making delivery "durable" and said only "a poison entry that never succeeds" was discarded. Nothing on the drain path distinguishes a poison payload from a down receiver — both are `Attempt::Transient` — and genuine poison is already handled separately and immediately as a `4xx` (`Attempt::Rejected`), so the attempt cap was defending a case that was already covered while silently destroying good records. Found on a production box where the audit receiver had been down: the spool sat at a flat 126 entries spanning exactly 7.9 hours (steady state *at* the horizon, entries ageing out as fast as they arrived) with **270 audit events permanently lost in a week**, the only signal being a warning that named a limit appearing nowhere in the config. The horizon is now wall-clock and per-sink: **`spool_max_age_secs`**, default **259200 (72 h)** so an outage starting on a Friday evening is still recoverable on Monday, and **`0` disables the age check entirely** — the correct setting for an audit stream that must not lose records, leaving the 10,000-entry file cap as the only bound. Available on all four spooling sinks (`[webhooks]`, `[cdr.webhook]`, `[audit.webhook]`, `[quality.webhook]`). The drop log line now names the limit that fired (`spooled delivery exceeded spool_max_age_secs`, with `age_secs` and `spool_max_age_secs`) rather than saying "max attempts", which read as the configured `retry_max` — a different counter at a different layer, and the confusion that led to finding this. Both the 8-hour horizon and the previously-undocumented 10,000-entry file cap are now in `CONFIG.md` and `DEPLOY.md`.

- **`siphon_ai_webhook_spool_depth` and `..._deliveries_total` HELP text listed only two of four sinks.** Both said "(lifecycle, cdr)" while `audit` and `quality` also use them — the audit sink being the one that mattered above.

---

**Artifacts**

- Static musl binaries for `x86_64` and `aarch64` (`.tar.gz`).
- Debian packages (`.deb`) for `amd64` and `arm64` — `apt install ./siphon-ai_*_amd64.deb`.
- `SHA256SUMS` — verify with `sha256sum -c SHA256SUMS`.
- `*-sbom.cdx.json` — CycloneDX SBOM.
- `SHA256SUMS.cosign.bundle` — cosign keyless signature over the checksums.
- Container: `ghcr.io/thevoiceguy/siphon-ai:v0.48.12` (multi-arch, cosign-signed).

Verify the checksums signature:
```
cosign verify-blob --bundle SHA256SUMS.cosign.bundle \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'release.yml@' SHA256SUMS
```

Related Repositories