A sound implementation does not ask users to refresh until it works. Each phase has an input, an output, a timeout, and a terminal state. Transport rarely gives exactly once, but user-visible effects can be idempotent. Persistently claim a stable event ID before showNotification.
Testing combines state models, fault injection, and real browser pairs. Deterministic cases protect known contracts, randomized timing finds races, and every failing seed plus endpoint trace becomes a permanent regression.
Define the system contract first
Start from facts the data and protocol can guarantee, then decide what the interface may promise. Each rule below needs an owner, a bound, and a compatibility policy rather than an oral convention from one review.
- Tabs compete for ownership through BroadcastChannel or a storage transaction; the Service Worker shares dedupe storage and uses the event ID as tag.
- Bound every input by size, count, and time, returning a stable actionable error code when a budget is exceeded.
- Use explicit capability negotiation so older clients receive an explained fallback instead of a half-working state.
The delivery standard for Testing Exactly-Once Notification Effects over Duplicate Delivery is a usable normal path, convergent failures, bounded resources, and a state users can understand. The result is a production capability that can be explained, degraded safely, and rolled back—not a demo that works once.
What can break that contract
Failure and success must share one state model. An error toast that neither releases resources nor propagates a terminal state leaves dirty work for the next recovery attempt.
- An in-memory Set vanishes when the worker is reclaimed, while show-before-persist duplicates during a crash window—the classic two alerts for one hello.
- A stale response arriving after a new task can overwrite healthy state or restart cancelled work without version fencing.
- User or task IDs in metric labels create high-cardinality cost and leak unnecessary identity into diagnostics.
How to test the contract line by line
A release gate combines deterministic regression, randomized timing, and real browser pairs. Preserve the seed and state trace from every failure as a permanent replay case.
- Deliver one event to two tabs and a worker, terminate before and after display, then replay; the system tray must contain one active notification.
- Run one hundred start, fail, retry, and cancel cycles; handles, listeners, queues, and temporary data must return to baseline.
- Before release, record success rate, p50/p95/p99 latency, error classes, and resource high-water marks with explicit rollback thresholds.
The result must be correct, recoverable, and explainable. If any part depends on refreshing the page or an engineer guessing, the protocol loop remains incomplete.