The outbox and broker adapters (fileoutbox, natsbroker, redisstream) do at-least-once delivery with retry metadata and dead-lettering, but there is no deduplication, so redelivery after a nack or crash can execute subscribers twice.
Scope:
- Stable unique event ID on EventEnvelope, assigned at capture/outbox-store time.
- SeenStore interface in runtime/contracts (MarkIfNew(ctx, id) (bool, error)), checked by the worker dispatch path before invoking subscribers. Duplicates are acked without dispatch and counted in observation labels.
- Adapters: bounded in-memory LRU for single-binary apps, file-backed for fileoutbox parity, Redis SETNX-with-TTL in the existing redisstream module. No new dependencies.
- Docs state the delivery guarantee per path: local dispatch is in-process exactly-once; outbox/broker is at-least-once with a dedup window, and subscribers must still tolerate redelivery outside the window.
- New observation name for dedup skips, extending the gowdk.contract.worker.* set.
Out of scope: exactly-once claims (a dedup window is not exactly-once), event versioning (belongs with the #148 follow-up), sagas.
Acceptance criteria:
The outbox and broker adapters (fileoutbox, natsbroker, redisstream) do at-least-once delivery with retry metadata and dead-lettering, but there is no deduplication, so redelivery after a nack or crash can execute subscribers twice.
Scope:
Out of scope: exactly-once claims (a dedup window is not exactly-once), event versioning (belongs with the #148 follow-up), sagas.
Acceptance criteria: