Saga teardown destroys reused infra on duplicate domain.create

closed
#5e674f6 opened by agent Sep 18

Follow-up from Task 4 spec review (backup-MX bug d43eebe).

Problem

ProvisionDomain compensations unconditionally tear down Stalwart domain + DKIM + route record + postmaster account on any post-step-1 failure, but Ensure’s changed flag is discarded (dm, _, err), so compensation cannot tell created from reused infra.

Concrete trigger

Double-submit of domain.create after success (double-click, client timeout-retry): second run reuses Stalwart domain + DKIM (idempotent) + postmaster (ErrExists path), overwrites the route record, then CreateDomain hits the domain TEXT UNIQUE constraint -> compensatePostmaster() deletes the LIVE route record, deactivates the LIVE postmaster, destroys LIVE DKIM + Stalwart domain, leaving the DB row dangling at a dead StalwartID. Legacy path on the same input is a harmless UNIQUE error.

Fix sketch

Thread Ensure’s changed bool through (skip Stalwart/DKIM teardown when reused); on CreateDomain failure, look up the domain name first - if a row already exists, skip all compensation (infra is live and owned).

Gate

Fix before enabling PDS.Supervision in production. Saga is currently gated behind supervision (default OFF).

1 Comment

agent 5de1687 Sep 20

Fixed and verified (commit 53efea94). Duplicate domain.create is now idempotent (same-tenant → existing id/state 200; cross-tenant → 409 ErrDomainExists), with a by-name pre-check plus a concurrent-race backstop. Compensations are provenance-aware: DKIM and the Stalwart domain are only torn down when this run created them; route-record delete + postmaster deactivate only when the postmaster was freshly created. Review follow-ups closed (partial-DKIM orphan cleanup, compCtx backstop lookup, empty-identity leak, cross-tenant backstop compensation, domain normalization, IsUserInputError). Full suite + vet green. Reconciliation/sweeps are deliberately out of scope; tracked in epic 516fcde subtask 9f75370. Closing.