Skip to content

Consider unifying the three dpp-types outbox traits (webhook/registry-sync/snapshot) #43

Description

@LKSNDRTMLKV

Context

The redundancy audit (docs/audit/dpp-engine/audit/redundancy-optimization-backlog-2026-07-20.md, Track A item #12) flagged WebhookOutbox, RegistrySyncOutbox, and SnapshotOutbox in dpp-types as duplicated, and marked it (defer). This item was scoped in detail (types, Postgres DAL, drain worker, migrations, and tests read in full for all three) before deciding not to pursue a full unification now. Filing this so the finding isn't lost.

What's shared

All three follow a "transactional outbox" shape: a Postgres table, a status enum (Pending → terminal success / terminal failure), an exponential-backoff retry policy with byte-identical SQL across all three mark_attempt_failed implementations, a counts struct for boot-time gauges, and a drain worker loop.

Why a generic trait isn't a clean fit

  • Registry sync's commit_publish writes the passport row and enqueues the registration in the same database transaction — webhook and snapshot both enqueue after-commit. This is load-bearing (a missed EU registration is a legal violation, not just a missed notification).
  • Registry sync retries forever — no attempt cap, no Exhausted state. Webhook and snapshot both cap at MAX_ATTEMPTS = 8.
  • Webhook rows freeze their exact payload to replay verbatim; snapshot rows carry no payload at all and deliberately re-derive put-or-remove from the passport's current status at drain time, specifically so replays/reordering can never apply a stale action. Opposite design choices for the same-looking row.
  • Different row identity (delivery id vs. passport_id UNIQUE) and fan-out cardinality (webhook is 1-to-many, the other two are 1-to-1).
  • Each has its own side-channel with no equivalent in the others: registry sync's status_intent column (migration 0024), snapshot's enqueue_divergent repair sweep.

The one place with genuinely zero semantic difference: the back_off_or_exhaust helper is byte-identical between crates/dpp-node/src/infra/webhook_drain.rs and crates/dpp-node/src/infra/snapshot_drain.rs (~20 lines) — a safe, low-effort extraction behind a small two-method trait, whenever someone is already touching both files for another reason. Not big enough to justify its own branch on its own.

Recommendation

Leave the three implementations independent. Revisit only if a fourth outbox-shaped consumer shows up (e.g. the EU registry status-push path the registry_sync.rs docs already gesture at as a future "Phase-B" addition) — that would be real evidence for a shared abstraction, where right now there are three data points each with a justified exception.

Priority

Optional / low priority. No functional or correctness issue — purely a "could this be less code" question, and the answer turned out to be "not much, safely."

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestwontfixThis will not be worked on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions