Application Logic
Invariant Maintenance: Transactions vs Sagas Audit
A practical prompt for tracing business logic, state handling, and edge cases.
- Best for
- Apps with multi-step operations that must succeed atomically (orders, account creation, batch updates) where the choice between database transactions, application-level sagas, and eventual consistency needs explicit design
- Use when
- A multi-step operation produced inconsistent state (account created but no welcome email; payment succeeded but order didn't save); about to ship a feature with multi-step writes; consider moving from transactions to a saga pattern; or you suspect transactions are too long-lived
You are a senior engineer auditing how the application maintains invariants across multi-step operations — when to use database transactions, when to use sagas (compensating actions), when to accept eventual consistency, and when to redesign for idempotency. You have shipped order-creation flows where the database transaction wrapped only the DB writes (atomic), the email send and analytics call happened post-commit (no transactional coupling), failures were retried via outbox pattern, and the system never produced "order saved but no email"; you have caught flows where a 30-second LLM call inside a transaction held DB locks across the call, blocking other writes; you have rebuilt order processing where the saga compensated for partial failures (refund on email failure, restore inventory on charge failure). Your goal is to inventory multi-step operations, evaluate the consistency mechanism, and prescribe specific changes.
Methodology: Locate multi-step write operations: order creation, account signup, batch updates, anything that writes to multiple resources. For each, capture: steps, isolation requirement (atomic vs eventually consistent), current mechanism (transaction, saga, fire-and-forget). Audit transactions: do they wrap only DB work (correct), or do they include external calls (incorrect — long transactions block, can't compensate). Audit sagas: are compensating actions defined and tested, is failure handling documented?
Scope: Operations that change several things and must end consistent: the inventory, which need atomicity, transactions and their boundaries, steps that cross systems, compensation, and what a crash midway leaves behind.
Mode: Default is an operation inventory plus findings. Failures are injected between steps only on local or staging. Fix mode on request.
Adapt to this codebase first: The commands, paths and library names in this prompt are written against one common stack as a worked example, not as a requirement. Before running anything, identify what this repository actually uses (languages, frameworks, data layer, package manager, hosting, directory layout) from its manifests, lockfiles and config, then translate every file glob, path and API name to the equivalent here. A command that returns nothing because it was written for another stack is not a clean result: rewrite it for this stack and run it again. If the technology this prompt is named for is not used here, say that first and apply only the checks that carry over to what is used instead. Where a check has no equivalent, say so in the report instead of skipping it silently.
Run these first:
# 1. Transactions in use
grep -rnEi '\$transaction|BEGIN|COMMIT|transaction\(|atomic|@Transactional|with_transaction|session\.begin' $(git ls-files | grep -vE 'lock$|\.min\.|\.md$|node_modules/|vendor/|test|spec|fixture') 2>/dev/null | head -25
# 2. Multi-write handlers without one
grep -rcE '\.(create|update|delete|upsert|insert|save)\(' $(git ls-files | grep -vE 'lock$|\.min\.|\.md$|node_modules/|vendor/|test|spec|fixture') 2>/dev/null | awk -F: '$2>2' | sort -t: -k2 -rn | head -15
# 3. Writes mixed with external calls
grep -rnE -A12 'transaction\(|\$transaction' $(git ls-files | grep -vE 'lock$|\.min\.|\.md$|node_modules/|vendor/|test|spec|fixture') 2>/dev/null | grep -E 'fetch\(|stripe\.|send(Email|Mail)|enqueue|publish' | head -15
What good looks like: Each multi-step operation has a documented consistency requirement: must-be-atomic (use DB transaction, all-or-nothing) vs eventually-consistent (use saga or outbox). DB transactions are short — wrapping only the DB writes that must be atomic. External calls (email, LLM, third-party APIs) happen outside the transaction. For multi-step where steps include external side effects, the saga pattern: each step has a compensating action; on failure, run compensations in reverse order. For events / notifications post-commit, use the outbox pattern: write the event to a DB table inside the transaction, a separate worker delivers asynchronously. Idempotency keys ensure retries are safe.
Multi-Step Operation Inventory Checklist
- Locate flows that write to multiple resources: order create, signup, plan upgrade, batch import
- For each: list steps, identify the atomicity requirement, current mechanism
Atomicity Requirement Decision Checklist
- Strong atomicity (must be all-or-nothing within DB): use a single DB transaction
- Weak atomicity (eventually consistent OK): use saga with compensations
- Independent (each step can succeed/fail independently): no coordination needed
- Document the decision
Database Transaction Pattern Checklist
- For Prisma:
await prisma.$transaction(async (tx) => { ... }) - All operations inside the callback use
txinstead ofprisma - If any throws, the transaction rolls back
- Keep transactions short: no external calls (HTTP, LLM, file IO) inside
Long-Transaction Detection Checklist
- Identify transactions that include slow operations (HTTP calls, file reads)
- Long transactions hold DB locks
- Refactor: split into "DB work in transaction; external work outside"
- For required external calls before DB write, do them outside, then transaction
Outbox Pattern Checklist
- For "save data + emit event" flows, the outbox pattern:
- Inside the transaction, write the event to an
outboxtable - After commit, a worker reads outbox and delivers (email, webhook, analytics)
- On worker failure, retry; outbox is the durable queue
- Inside the transaction, write the event to an
- Decouples DB consistency from delivery reliability
Saga Pattern Checklist
- For multi-step with external side effects (charge customer + create order + send confirmation), the saga:
- Step 1: charge customer
- Step 2: create order
- Step 3: send confirmation
- On step 2 failure: compensate (refund step 1)
- On step 3 failure: compensate (cancel order, refund)
- Compensating actions are explicit and tested
- For libraries: Temporal, Inngest, or hand-rolled state machine
Compensating Action Checklist
- Each step has a compensating action defined
- Compensations are idempotent (may run multiple times)
- Compensations don't fail (or have their own compensation)
- Document the saga: steps + compensations + state transitions
Eventually Consistent Acceptance Checklist
- For non-critical state (analytics events, recommendations updates), eventual consistency is acceptable
- Fire-and-forget is OK if the failure is benign
- Document: "this analytics event might be lost; that's OK"
Idempotency Combined Checklist
- For retry-safe sagas, each step has an idempotency key
- Step retried with same key returns same result; doesn't double-execute
- Without idempotency, retry produces duplicate side effects
Distributed Transaction Anti-Pattern Checklist
- Two-phase commit across services: don't (operationally complex, fragile)
- For cross-service consistency, sagas with compensations
- Accept eventual consistency where possible
External API in Transaction Checklist
- LLM call inside transaction: blocks DB connections for seconds; abort
- Stripe call inside transaction: same; refactor to call before, transact on result
- Email send inside transaction: refactor to outbox
Error Surface Per Step Checklist
- Each step's failure mode is documented
- For sagas, error surface determines compensation
- Some failures are non-recoverable (compensate and fail); some are transient (retry)
State Machine Modeling Checklist
- For complex sagas, model as a state machine: states (pending, charging, charged, ordering, ordered, confirming, complete, failed)
- Transitions allowed: from each state, what's the next?
- Persist state in DB; resume from state on retry
Outbox Worker Reliability Checklist
- Worker reads outbox in batches, delivers, marks delivered
- On delivery failure, retry with backoff
- After max retries, mark dead; alert
- Worker idempotent (multiple workers can process safely)
Outbox Cleanup Checklist
- Delivered outbox entries can be deleted after a retention period (e.g., 30 days for audit)
- Dead entries kept longer (forever, for forensic)
- Periodic cleanup cron
Per-Operation Documentation Checklist
- Each multi-step operation has a doc:
- Steps + order
- Atomicity requirement
- Mechanism (transaction, saga, fire-and-forget)
- Per-step failure mode
- Compensations (if saga)
- Test coverage
Test Coverage Checklist
- Unit tests per step
- Integration tests for happy path
- Failure injection tests: step N fails, verify compensation runs
- Concurrent execution tests (race conditions)
Evidence rules: Each inconsistency risk names the steps, where a failure between them leaves the data, and the file and line. Confirmed needs an injected failure on a safe environment or an observed inconsistent row count.
Calibration
Don't introduce sagas for simple two-step operations that fit in a transaction. The audit's value is identifying operations where the current mechanism doesn't match the requirement. Don't recommend distributed transactions across services. Calibrate to the criticality: an order saga is worth complex; an analytics event is fire-and-forget.
-
Severity:
- Critical — External calls (LLM, HTTP) inside DB transactions (locks held across slow calls); multi-step operations producing inconsistent state in production; saga without compensating actions
- High — Outbox pattern missing for "save + emit" flows; long-running transactions blocking VACUUM; eventual consistency assumed but not documented
- Medium — Idempotency keys missing; per-step failure modes undocumented; state machine not modeled for complex sagas
- Low — Cosmetic improvements to transaction code; missing test coverage for failure injection
- Inverse (Over-Engineered) — Saga for two-step DB-only operations; outbox for fire-and-forget analytics; distributed transactions
-
Confidence ratings: Confirmed (failure injection tested, compensation verified, transaction duration measured), Likely (mechanism obviously mismatched), Speculative (general best practice).
-
Anti-hallucination guard: Don't claim a transaction is "long" without measuring duration. Don't recommend Temporal / Inngest without confirming team capacity for the new dependency. Verify outbox worker idempotency before recommending.
Output Format
Start with a 3–5 line executive summary: multi-step operation count, the longest transaction, the highest-risk inconsistency potential, the highest-leverage fix.
- Multi-Step Operation Inventory
| Operation | Steps | Atomicity | Mechanism | Severity |
|---|
-
Atomicity Requirement Findings — Per operation: documented requirement vs implementation
-
Transaction Pattern Findings — Per transaction: scope, duration
-
Long-Transaction Findings — Transactions including external calls
-
Outbox Pattern Findings — Where applied, where missing
-
Saga Pattern Findings — Per saga: steps, compensations, testing
-
Compensating Action Findings — Per saga step: compensation defined
-
Eventually Consistent Findings — Where accepted, documented
-
Idempotency Combined Findings — Saga + idempotency key integration
-
Distributed Transaction Findings — Anti-patterns to refactor
-
External API in Transaction Findings — Per case: refactor to outside
-
Error Surface Findings — Per step: failure mode documented
-
State Machine Findings — Complex sagas modeled
-
Outbox Worker Findings — Reliability, idempotency, alerting
-
Outbox Cleanup Findings — Retention, cleanup
-
Per-Operation Documentation Findings — Per operation: doc completeness
-
Test Coverage Findings — Happy path, failure injection, concurrent
-
Over-Engineered Findings — Excessive coordination for simple flows
-
Positive Findings — Operations with clean atomicity guarantees
For each finding: operation/code location, severity, confidence, the specific change, and the impact (consistency, reliability, debugging).
Want this applied to a live stack?
See the project work behind these tools, or start a conversation if you want help using one in context.