The outbound audit row was written by an inert REQUIRES_NEW

FixSecurityService
Shipped
August 27, 2026 at 9:09 AM UTC
Author
Kamo
Commit
31379b6

It was a method on ExchangeQueryService annotated @Transactional(REQUIRES_NEW), called from two private helpers on the same class. Spring's transaction advice lives on a proxy and a self-invocation never reaches it, so the annotation did nothing at all: the row joined the caller's transaction. That is precisely backwards for what it records. A FAILED outbound query would have rolled back the record of its own failure — and "we asked and they refused" is the row somebody investigating a gap in a patient's history actually needs. A ledger of only the successful queries answers the easy question and loses the hard one. Worse still, discover() and queryDocuments() are readOnly = true, so the write had no business being in their transaction at all. Extracted to ExchangeRequestRecorder, a bean of its own, exactly as BulkExportJobState was earlier for the same reason. A unit test cannot catch this — constructing the service directly means there is no proxy either, which is why it passed while being wrong. So the guard is structural: the recorder's test asserts the method is PUBLIC (a non-public method is never proxied, making its annotation inert) and that its propagation is REQUIRES_NEW. Both halves are invisible at a call site and both have to hold.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing