Skip to content

fix(integration): harden inbound ingress idempotency and delivery durability#591

Merged
rmyndharis merged 1 commit into
mainfrom
fix/ingress-durability-idempotency
Jul 2, 2026
Merged

fix(integration): harden inbound ingress idempotency and delivery durability#591
rmyndharis merged 1 commit into
mainfrom
fix/ingress-durability-idempotency

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Summary

Several inbound-webhook (ingress) durability and idempotency gaps in the Integration Fabric.

Changes

  • Dedup key includes pluginId. The inbound dedup unique key was (instanceId, providerDeliveryId), but instanceId is only unique within a plugin — two plugins sharing an instanceId string would drop each other's deliveries as false duplicates. Widened to (pluginId, instanceId, providerDeliveryId) via a portable DROP/CREATE unique-index migration (loosening only — no data loss) plus the matching entity index.
  • Deterministic delivery id when the provider sends no dedup header. Previously a random UUID was used, which silently disabled both persist-dedup and BullMQ jobId idempotency, so a provider retry of the same body was treated as new → duplicate WhatsApp sends. Now derived from sha256(pluginId · instanceId · route · rawBody) (no timestamp) so a retry dedups.
  • Redrive no longer loses events on a silent dispatch failure. IngressEnqueueService.enqueue now returns a {queued|dispatched|failed} outcome (still never throws); redrive only marks a DLQ row handled when the replay was actually accepted, so a swallowed inline-dispatch failure keeps the row redrivable.
  • Race-safe conversation mapping upsert. findOne-then-save was a TOCTOU that raised an uncaught QueryFailedError (DLQ'ing the job) under a concurrent write or a reverse-unique collision. Now insert-or-converge: a forward-key race converges by updating; a genuine reverse-unique conflict throws a typed ConversationMappingConflict. Extracted a shared isUniqueViolation helper.

Verification

npm run build ✓ · npm test ✓ (1849/1849, +4 new) · lint ✓ · migration:show lists the new migration. New/updated tests cover the cross-plugin dedup key, deterministic id (retry dedups, different body differs), redrive-keeps-row-on-failure, and the reverse-unique conflict.

…ability

- Include pluginId in the ingress dedup unique key (pluginId, instanceId, providerDeliveryId).
  instanceId is only unique within a plugin, so two plugins sharing an instanceId string dropped
  each other's deliveries as false duplicates. Portable DROP/CREATE index migration + entity index.
- Derive a deterministic delivery id (sha256 of pluginId/instanceId/route/rawBody) when the provider
  sends no dedup header, instead of a random UUID that silently disabled dedup and BullMQ job
  idempotency and caused duplicate sends on provider retries.
- IngressEnqueueService.enqueue now returns a {queued|dispatched|failed} outcome (still never throws);
  redrive only retires a DLQ row when the replay was accepted, so a swallowed inline-dispatch failure
  keeps the row redrivable instead of permanently losing the event.
- Make conversation-mapping upsert a race-safe insert-or-converge: a forward-key race converges by
  updating; a genuine reverse-unique conflict throws a typed ConversationMappingConflict instead of an
  uncaught QueryFailedError that DLQ'd the job. Extract a shared isUniqueViolation helper.
@rmyndharis rmyndharis merged commit aca9fb4 into main Jul 2, 2026
3 checks passed
@rmyndharis rmyndharis deleted the fix/ingress-durability-idempotency branch July 2, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant