fix(documents): close v1.25.1 library QA findings (backend)

Default documentDate at store time. A store-only upload no longer lands
with a NULL filing date: when the client omits one, default it to the
upload day so the column is always set on a stored row. The library list
sorts, groups, and range-filters on the same effective date the UI shows,
so undated uploads no longer mis-sort under nulls-last, break date-group
headers, or fall through the range filter. The date stays user-editable
and may be cleared back to NULL via PATCH.

Block re-extraction once any fact is APPROVED. A partially-confirmed
document stays EXTRACTED, which the CONFIRMED guard does not catch;
re-extracting would delete the approved facts (severing the committed
record's provenance) and re-stage them as PENDING, letting a second
approval duplicate the committed lab / condition / medication. The
extract route now refuses with 409 (documents.inbound.alreadyPartlyConfirmed)
when approved facts exist; the user must finish reviewing or discard first.

RFC 5987-encode the original-download filename. The Content-Disposition
now carries both an ASCII filename= fallback and a filename*=UTF-8'' value
so a non-ASCII name survives intact; the stored set is inline-safe by
construction, so the dead attachment branch is removed.

Add a light per-user rate-limit backstop to the original-read, PATCH, and
DELETE routes (generous; self-DoS only — the routes are owner-scoped).

Lean the list counts: tally facts for the whole page in one grouped count
instead of materialising fact-id rows, and exclude REJECTED facts from
factCount. Extend the documentDate index with deleted_at to cover the
list scan. Document the deliberate plaintext-title PHI tradeoff at the
column and the store site.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 23:48:43 +02:00
1 parent 84543ad540
commit eb1779d5c1
14 files changed
+317 -94

No files matched your search

+14 -8
View File
@@ -8714,7 +8714,8 @@ paths:
- VACCINATION
- OTHER
documentDate:
description: Optional user filing date (YYYY-MM-DD).
description: Optional user filing date (YYYY-MM-DD). Defaults to the upload day when omitted so the library
sort/filter/display stay aligned.
type: string
required:
- file
@@ -8828,10 +8829,10 @@ paths:
- Documents
summary: View / download the original document
description: "Decrypts and serves the raw uploaded document (the bytes stored encrypted at rest under
`InboundDocument.contentEncrypted`). Owner-scoped + gated on the opt-in `inboundDocuments` module. The response
carries the stored `mimeType`: a PDF / image is served inline (`Content-Disposition: inline`) so the browser can
render it; any other type downloads as an attachment. Fails closed (500) on a decrypt error — it never returns
the ciphertext."
`InboundDocument.contentEncrypted`). Owner-scoped + gated on the opt-in `inboundDocuments` module. Uploads are
constrained at the store path to the inline-safe set (JPEG / PNG / WebP / PDF), so the document is always served
`Content-Disposition: inline` for in-tab rendering; a non-ASCII filename is carried via RFC 5987 `filename*`.
Fails closed (500) on a decrypt error — it never returns the ciphertext."
parameters:
- name: id
in: path
@@ -8850,9 +8851,6 @@ paths:
schema:
type: string
format: binary
text/plain:
schema:
type: string
"401": *a1
"422": *a3
"429": *a4
@@ -8888,6 +8886,14 @@ paths:
schema:
$ref: "#/components/schemas/InboundExtractEnvelope"
"401": *a1
"409":
description: "Re-extraction refused: at least one fact on this document is already APPROVED. `meta.errorCode` =
`documents.inbound.alreadyPartlyConfirmed`. Re-extracting would sever committed-record provenance and
duplicate committed records, so the user must finish reviewing or discard the document first."
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/documents/inbound/{id}/facts/{factId}: