Bulk export, C-CDA, and one accounting that covers every channel

FeatureSecurityService
Shipped
August 26, 2026 at 5:10 AM UTC
Author
Kamo
Commit
dadd154

BULK EXPORT. Kickoff answers 202 with a Content-Location and no body; polling answers 202 while running and 200 with the manifest when done. Returning a friendlier 200-with-status from the kickoff — the obvious improvement — makes every conformant client treat the export as finished and fetch a manifest that does not exist yet. Prefer: respond-async is required for the same reason: it is the client asserting it knows this is asynchronous. Gated on EXPORT_PATIENT_DATA and nothing else, deliberately. Every other clinical read is bounded by a care relationship with a named patient; a bulk export names no patient — that is what makes it bulk — so there is no relationship to check and the right IS the gate. ClinicalRightsGuard exists because the EHR rights were declared and nothing enforced them, which is fine where the care-relationship check stands behind them and is nothing at all on the population-level surfaces. The worker's state changes live in their own bean. Written as private methods on the runner they would have looked transactional, been annotated REQUIRES_NEW, and silently run in the caller's context — Spring's proxy does not intercept a call from one method of a bean to another. This platform has shipped that bug before. Each file is written to storage BEFORE its row is saved: a row pointing at a missing object makes a manifest whose links 500, which reads as data loss to the receiving system. C-CDA. What the hospital down the road can import. Recipient is a required parameter because generating a summary is a DISCLOSURE, and the accounting has to answer "who has my record" with a name a patient recognises. THE ACCOUNTING. DisclosureLedger existed and nothing wrote to it, while the §164.528 endpoint read exchange rows only — so a patient asking who has their record was shown the TEFCA responses and nothing else. A bulk export to their health plan, a records release, a portal download: none appeared, and nothing said the list was partial. An accounting that is silently incomplete is worse than one that is missing, because it is believed. Every channel now writes through DisclosureRecorder, and the endpoint reads the ledger. The recorder writes in its OWN transaction. A disclosure that happened and was not recorded is the failure it exists to prevent; the converse over-reports, which is a question somebody answers rather than a regulation somebody breaches.

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