Follow-up from review of #33.
Problem
The webhook ingest pipeline (apps/worker/src/ingest.ts) reads the previous summary from KV, detects a status change, and writes the new summary — a read-modify-write that is not atomic. The cron path is serialized by the scheduler so it's unaffected, but the new webhook path can receive multiple in-flight requests simultaneously.
If two rapid-fire webhooks land for the same site (or Statuspage retries quickly), both requests can read the same prior status, both pass the prior.status !== r.status gate, and both call dispatchNotifications → subscribers receive duplicate alerts.
Note the final KV state stays consistent (both writes carry the same new status); only notification delivery is affected — i.e. current semantics are at-least-once, and cron reconciles the summary regardless.
Options
- Durable Object per page/site to serialize the read-modify-write + change detection.
- Workers Queue between ingest and
dispatchNotifications with an idempotency key (dedupe by slug + from→to + coarse timestamp). This also aligns with the existing note in notify.ts/wrangler.jsonc about putting a Queue between producer and dispatch for retries/backpressure.
Scope
Deferred from #33 (status-only webhook ingest). Low harm today (duplicate alert, not data corruption), so tracking rather than blocking.
Follow-up from review of #33.
Problem
The webhook ingest pipeline (
apps/worker/src/ingest.ts) reads the previous summary from KV, detects a status change, and writes the new summary — a read-modify-write that is not atomic. The cron path is serialized by the scheduler so it's unaffected, but the new webhook path can receive multiple in-flight requests simultaneously.If two rapid-fire webhooks land for the same site (or Statuspage retries quickly), both requests can read the same prior status, both pass the
prior.status !== r.statusgate, and both calldispatchNotifications→ subscribers receive duplicate alerts.Note the final KV state stays consistent (both writes carry the same new status); only notification delivery is affected — i.e. current semantics are at-least-once, and cron reconciles the summary regardless.
Options
dispatchNotificationswith an idempotency key (dedupe byslug + from→to + coarse timestamp). This also aligns with the existing note innotify.ts/wrangler.jsoncabout putting a Queue between producer and dispatch for retries/backpressure.Scope
Deferred from #33 (status-only webhook ingest). Low harm today (duplicate alert, not data corruption), so tracking rather than blocking.