API Testing / Editorial solution guide
Diagnose duplicate effects after an uncertain timeout
Separate dispatch attempts, replay decisions, durable commits, and downstream delivery before choosing a repair.
Read-only knowledge base · No public comments
Recognize the symptoms
A client times out, retries, and later sees two entries or two notifications. Responses may even contain the same identifier. Preserve the synthetic action id, attempt sequence, interruption point, and final protected state. A timeout itself does not establish that the first action failed, and equal responses do not establish unique business effects.
Understand the likely causes
- The first commit may have succeeded before response loss, a replay key may have the wrong scope or payload binding, concurrent workers may race through check-then-insert, or a downstream consumer may duplicate its effect. Short retention and incorrectly classified permanent errors can also contribute. These are competing hypotheses until the observed effect boundary distinguishes them.
Reconstruct attempts and committed actions
Build a minimal timeline from approved existing diagnostics using nonsecret synthetic identifiers. Distinguish request received, replay lookup, business commit, response emission, message delivery, and notification commit. Do not assume one log line equals one durable action. Compare protected state and relevant outbox or consumer records where authorized evidence already exists. This guide uses local invented records only and does not query a configured service. If the actual outcome is unknown, follow the documented reconciliation process rather than launching another operation to discover it.
Find the first boundary with duplicate effects
The example contains two request attempts, one entry commit, and two notification commits. That observation points investigation toward the downstream effect boundary rather than proving duplicate entry creation. It does not explain why the notification repeated; delivery, deduplication, and transaction boundaries still need evidence. A local count is useful because it prevents attempts and effects from being merged into one misleading number. Preserve each event’s scope and business identifier, and do not use a request correlation id as a substitute for a documented deduplication identity.
events = [("attempt", "action-4"), ("entry-commit", "action-4"), ("attempt", "action-4"), ("notification-commit", "action-4"), ("notification-commit", "action-4")]
counts = {kind: sum(k == kind and action == "action-4" for k, action in events) for kind in ("attempt", "entry-commit", "notification-commit")}
assert counts == {"attempt": 2, "entry-commit": 1, "notification-commit": 2}
print("entry unique; notification boundary duplicated")
entry unique; notification boundary duplicated
The complete program counts five local records and checks their declared meaning. It does not prove real log completeness, delivery guarantees, or durable deduplication. No retries, requests, workers, or messages are actually dispatched.
Repair the responsible boundary narrowly
For entry duplication, review the atomic relationship between replay reservation and the business commit. For downstream duplication, review the consumer’s effect identity, retention, and crash recovery boundary. Bind keys to the approved caller and operation scope, reject changed-payload reuse, and define in-progress behavior. Do not suppress every notification or accept an arbitrary client assertion that work already happened. Do not claim distributed exactly-once execution from one successful replay. A reviewed design should name which duplicate effects it suppresses and what recovery does when persistence or delivery is interrupted.
Verify the fix
Verify a controlled synthetic lost-response case, identical replay, changed-payload conflict, distinct caller scope, and duplicate downstream delivery at the appropriate approved boundary. Require the documented final entry and notification counts, not merely equal response bodies. Record actual executed evidence and explicitly label untested persistence, concurrency, and crash-recovery assumptions. The local counting example establishes only its fixture interpretation.
Keep it from recurring
- Keep retry classification, deadlines, replay lifecycle, and consumer effect identity in the versioned contract. Distinguish attempts from commits in diagnostics and retain negative regression cases for payload mismatch and denied subjects. A timeout remains uncertainty until reconciled; operational recovery must follow the approved policy rather than improvising repeated work.