# Transaction Guide Design §15-§20. What owns a transaction, what may be retried, and what must never be. ## The application service owns the boundary Repository adapters do not open transactions. The use case does, through `TransactionPort` or `JpaTransactionExecutor`, because the unit of work is a business decision and only the use case knows where it starts and ends. Open Session In View is off in every runtime profile. It is on by default in Spring Boot, which is why `JpaDangerousConfigurationGuard` fails startup rather than trusting configuration review. ## Profiles A `TransactionProfile` fixes propagation, isolation, timeout, read-only, and the retry budget. A write profile must carry a positive finite timeout — the type refuses to represent one without — because an unbounded write transaction holds a connection, its locks, and its row versions for as long as one stuck statement takes. `REQUIRES_NEW` is opt-in. It acquires a second physical connection while pinning the first, so a profile using it must be paired with the pool-pressure evidence in design §38: ```text maximumPoolSize >= (concurrent_threads x (1 + max_requires_new_depth)) + 1 ``` ## Retry is per use case, never per statement `FullTransactionRetryCoordinator` re-enters the executor, which produces a new transaction and a new Persistence Context for every attempt. That granularity is the whole point: an optimistic conflict means the state the attempt computed against is no longer the committed state, so re-issuing the same statement would compute the same wrong answer. The domain rules have to run again against reloaded data. Retryable: serialization failure (`40001`), deadlock (`40P01`), optimistic conflict. Not retryable: constraint violations, schema mismatch, query timeout, and anything unclassified. Two additional refusals, independent of budget: - An attempt that declared an irreversible external effect through `IrreversibleSideEffectContext`. Rollback reverses database work only; an email or a card charge has already changed the world. - Anything completion-unknown. ## Completion unknown `TransactionCompletionUnknownException` is never retried, and the type system enforces it twice: `JpaFailureContext` refuses to represent a retryable completion-unknown failure, and the exception rebuilds its context through the safe factory whatever it is handed. `EvidenceAwareJpaTransactionManager` marks the phase `COMMITTING` immediately before delegating to the provider commit and never after. If the network, the JVM, or the server dies inside that call, the last thing written is "we asked, we do not know" — which is exactly the state that must not be mistaken for a rollback. Recovery is reconciliation, not retry: ```text record the transaction key -> check the idempotency record -> check the business row -> check the outbox -> still undetermined? reconciliation queue ``` `CompletionUnknownRecorder` writes that record through a channel outside the unknown transaction. Writing it through the same connection would make the audit trail share the failure it documents.