Transaction-Sensitive Operations
Identifies related writes that must commit or roll back together to protect data consistency.
Scenario
You maintain a financial application where some operations modify several database records.
For example, transferring funds may require:
debit source account
credit destination account
create transfer record
create audit entry
These writes represent one logical operation.
Partial success could leave the system inconsistent.
You want coding agents to recognize transaction-sensitive workflows and preserve atomicity.
Repository Structure
payments-api/
├── src/
│ ├── services/
│ │ └── transfer-service.ts
│ ├── repositories/
│ │ ├── account-repository.ts
│ │ ├── transfer-repository.ts
│ │ └── audit-repository.ts
│ └── db/
│ ├── client.ts
│ └── transaction.ts
├── tests/
│ └── integration/
└── AGENTS.md
AGENTS.md
# Project Instructions
## Transactions
Use the existing transaction helper when multiple database operations form one atomic business operation.
Examples include:
- balance transfers;
- inventory reservation with order creation;
- related record creation that must succeed together.
## Transaction Context
- Pass the active transaction context to repository operations.
- Do not start unrelated database connections inside an active transaction workflow.
- Do not commit or roll back transactions from repository functions unless that repository explicitly owns the transaction boundary.
## Business Logic
- Keep transaction orchestration in the service/application layer.
- Keep individual persistence operations in repositories.
- Validate business preconditions before writes when possible.
## External Side Effects
- Be cautious about performing irreversible external side effects inside a database transaction.
- Do not assume a database rollback can undo an email, webhook, payment-provider request, or message already sent.
- Follow the repository's existing outbox, event, or post-commit pattern when one exists.
## Concurrency
- Preserve existing locking or concurrency-control patterns.
- Do not remove transaction isolation, row locking, idempotency, or version checks without understanding why they exist.
## Testing
For transaction-sensitive changes:
- test successful completion;
- test failure partway through the workflow;
- verify partial database state is not committed;
- test relevant concurrency or idempotency behavior when applicable.
What This Does
This tells the agent that some workflows have an atomicity requirement.
For a transfer:
BEGIN
debit account A
credit account B
create transfer
COMMIT
If creating the transfer record fails, the balance updates should not remain partially committed.
Conceptually:
all succeed
↓
commit
any fail
↓
rollback
What This Does NOT Do
The file does not say:
- Put every database query inside one transaction.
Transactions should protect operations that require atomic behavior.
Long or unnecessary transactions can introduce their own problems.
The file also does not pretend that database transactions cover external systems.
Suppose the workflow does:
update database
send payment-provider request
send email
Rolling back PostgreSQL cannot unsend an email or reverse an arbitrary external API request.
Those workflows require additional design considerations.
Why These Instructions Matter
Consider this implementation:
await accountRepository.debit(sourceId, amount);
await accountRepository.credit(destinationId, amount);
await transferRepository.create(transfer);
Suppose the final call fails.
Without a transaction:
source debited ✓
destination credited ✓
transfer record ✗
The database now contains incomplete business state.
Using a shared transaction context allows the operations to succeed or fail together.
Another subtle failure occurs when a repository ignores the transaction context.
For example:
service starts transaction
↓
repository A uses transaction
↓
repository B uses global DB client
Repository B's write may commit independently and survive a rollback.
That is why the instructions explicitly say:
- Pass the active transaction context to repository operations.
Key Decisions
Put transaction boundaries around business operations
The service layer often knows which writes collectively represent one operation.
That makes it a natural place to orchestrate the transaction.
Repositories participate in the transaction
Repositories should use the provided transaction context rather than silently escaping it.
Test rollback behavior
A happy-path test does not prove atomicity.
Useful testing intentionally causes failure after some operations have executed and verifies that partial state is absent.
Database atomicity is not distributed atomicity
External APIs, queues, emails, and payment providers require different reliability patterns.
Depending on the system, those may include:
outbox pattern
idempotency keys
retry handling
post-commit events
compensating actions
AGENTS.md should point agents toward whichever pattern the repository already uses.
Preserve concurrency protections
Code such as:
SELECT ... FOR UPDATE
version columns
idempotency checks
unique constraints
may exist specifically to prevent race conditions.
Do not simplify it without understanding the invariant being protected.
When to Use This Pattern
Use this pattern when:
- several writes represent one business operation;
- partial completion would corrupt business state;
- concurrent requests can modify the same records;
- financial, inventory, entitlement, or workflow state is involved;
- database operations interact with external side effects.
This is where AGENTS.md moves beyond file organization and starts preserving critical operational invariants.