Two export flows share one engine: the Medizinischer Dienst (MD) audit
package the PDL hands to a reviewer, and the GDPR Article 15 copy a patient
can request. Both include encrypted bytes the application already holds;
the Art. 15 variant pseudonymises foreign patient references and limits the
scope to one patient. Decision:
ADR-0030;
code:
packages/api/src/features/exports.
Container
Standard AES-256 .zip (AE-2) written with
@zip.js/zip.js's
streaming writer directly into R2 multipart. The package can be opened by
7-Zip with the password shown once in the UI; it is never stored. The MD
package carries intimate full originals under the organization intimate key
(OIK) decrypted inside a logged system job
(ADR-0017).
Entries inside the archive:
manifest.json— the authoritative map (entity, id, sha256 of the plaintext, chain pointer).patients/<id>/— one folder per patient in scope: metadata, consents, prescriptions, trail excerpts.wounds/<id>/— classification, assessments and measurements (every version), plan versions, dressing changes.photos/<id>/— the full originals (full.bin) and the crop / thumbnail used in the UI, each with its ownsha256.trail.jsonl— the per-Organization hash chain for the patients in scope so a reviewer can replay it independently.
Checkpointed resume
Building a package costs more than one tick's 25-second budget. The job
exportsBuild holds a lease on export_job and writes a state jsonb
checkpoint between entries. A tick that times out releases the lease; the
next tick continues from the checkpoint
(ADR-0022). A
crash discards the in-progress part and the next lease rebuilds it.
Fallback strategy for very large exports (around 412 MB for a long-running
organization): the engine switches to multi-volume output ("Teil n von m")
and uploads each volume separately. The spike that pins the strategy lives
under
docs/wundklar/measurements.
Lifecycle
exports.requestwrites anexport_jobunder the clinical boundary (ADR-0031); the reason is kept with the trail.- The user is shown the download link and the password on the desktop
reports page; the trail records the purpose
(
view_fullorlegal_export). - The archive expires after 72 hours; the retention job deletes it with a receipt (ADR-0033).