Merge branch 'feat/vault-w4-qa' into release/v1.27.17

This commit is contained in:
Marc-André Bombeck committed 2026-07-07 15:10:45 +02:00
commit 03058b0210
81 files changed
+10544 -3246

No files matched your search

+350 -24
View File
@@ -8669,20 +8669,39 @@ paths:
maxLength: 100
description: Case-insensitive search over title + filename.
- name: kind
in: query
required: false
style: form
explode: true
schema:
type: array
items:
type: string
enum:
- DOCTOR_REPORT
- DISCHARGE_LETTER
- LAB_RESULT
- IMAGING
- PRESCRIPTION
- REFERRAL
- INSURANCE
- VACCINATION
- OTHER
description: Category filter — OR inside the facet. Repeat the parameter (or send a comma-separated value).
- name: episodeId
in: query
required: false
schema:
type: string
enum:
- DOCTOR_REPORT
- DISCHARGE_LETTER
- LAB_RESULT
- IMAGING
- PRESCRIPTION
- INSURANCE
- VACCINATION
- OTHER
description: Category filter.
description: Only documents linked to this illness/condition episode.
- name: year
in: query
required: false
schema:
type: integer
minimum: 1900
maximum: 9999
description: Only documents whose filing date falls in this calendar year (UTC). Takes precedence over from/to.
- name: from
in: query
required: false
@@ -8746,9 +8765,16 @@ paths:
summary: Store a document (no extraction)
description: "STORE-ONLY upload. Stores the raw document ENCRYPTED at rest with `status: STORED` and runs NO extraction
— provider-free, no AI consent / budget / egress. A file is always filable, even with no document-scan provider
configured. `multipart/form-data`: a `file` (JPEG/PNG/WebP/PDF, validated by magic-byte MIME sniff, ≤ 12 MiB)
plus optional `title`, `kind`, and `documentDate` (YYYY-MM-DD) form fields. AI extraction is a separate opt-in
action — see `POST /api/documents/inbound/{id}/extract`."
configured. `multipart/form-data`: a `file` plus optional `title`, `kind`, `documentDate` (YYYY-MM-DD), and
repeated `episodeIds` form fields (pre-link to the caller's illness/condition episodes). Accepted types
(magic-byte sniffed, never the wire Content-Type): PDF/JPEG/PNG/WebP/GIF render inline; Office
(docx/xlsx/pptx/doc/xls/ppt), text/CSV/Markdown/RTF, TIFF, HEIC/HEIF, XML/JSON are stored verbatim and served
download-only. HEIC is stored as-is but attachment-only — prefer transcoding to JPEG client-side for inline
preview parity. Error contract: `413` with `meta.reason = \"fileTooLarge\"` (+ `maxFileBytes`) or
`\"quotaExceeded\"` (+ `quotaBytes`, `usedBytes`); `415` with `meta.reason = \"unsupportedType\"`. A same-user
duplicate (same bytes) returns 200 + `meta.duplicate: true` with the existing row — not an error.
`Idempotency-Key` honoured. Read `GET /api/documents/inbound/usage` for the effective limits before offering an
upload. AI extraction is a separate opt-in action — see `POST /api/documents/inbound/{id}/extract`."
requestBody:
required: true
content:
@@ -8759,7 +8785,7 @@ paths:
file:
type: string
format: binary
description: The document image or PDF. Validated by magic-byte MIME sniff, not the wire Content-Type.
description: The document. Validated by magic-byte MIME sniff, not the wire Content-Type.
title:
description: Optional user label.
type: string
@@ -8772,6 +8798,7 @@ paths:
- LAB_RESULT
- IMAGING
- PRESCRIPTION
- REFERRAL
- INSURANCE
- VACCINATION
- OTHER
@@ -8779,9 +8806,21 @@ paths:
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
episodeIds:
description: Optional condition pre-links (repeat the field per id). Every id must be a live episode of the caller.
type: array
items:
type: string
required:
- file
responses:
"200":
description: "Duplicate — the same bytes are already stored: the existing row, with `meta.duplicate: true` at the
envelope level."
content:
application/json:
schema:
$ref: "#/components/schemas/InboundDocumentEnvelope"
"201":
description: The stored document.
content:
@@ -8789,6 +8828,99 @@ paths:
schema:
$ref: "#/components/schemas/InboundDocumentEnvelope"
"401": *a1
"413":
description: "Too large: `meta.reason` is `fileTooLarge` (with `maxFileBytes`) or `quotaExceeded` (with `quotaBytes` +
`usedBytes`)."
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"415":
description: Unsupported or unidentifiable type (`meta.reason = "unsupportedType"`). Executables, HTML/SVG, and generic
archives are always refused.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/documents/inbound/usage:
get:
tags:
- Documents
summary: Storage usage + effective limits
description: "The caller's vault usage and effective limits: `usedBytes` (every non-purged row — tombstones inside the
30-day undo grace still count), `quotaBytes` (per-user override ?? instance default), `maxFileBytes`
(admin-tunable per-file cap), `acceptedExtensions` for picker `accept` lists, and `linkedEpisodes` (episodes
carrying at least one live document link — the condition-filter chips). Read this before offering an upload."
responses:
"200":
description: Usage + limits.
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentUsageEnvelope"
"401": *a1
"422": *a3
"429": *a4
/api/documents/inbound/bulk:
post:
tags:
- Documents
summary: Bulk action over documents
description: Applies one action to up to 100 owner-scoped documents and returns a per-id result array — a partial
failure never aborts the batch. `error` is `notFound` (unknown / foreign / tombstoned where liveness is
required) or `conflict` (restore blocked by a live duplicate of the same bytes).
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
ids:
minItems: 1
maxItems: 100
type: array
items:
type: string
minLength: 1
maxLength: 40
action:
type: string
enum:
- setKind
- linkEpisode
- unlinkEpisode
- delete
- restore
kind:
type: string
enum:
- DOCTOR_REPORT
- DISCHARGE_LETTER
- LAB_RESULT
- IMAGING
- PRESCRIPTION
- REFERRAL
- INSURANCE
- VACCINATION
- OTHER
episodeId:
type: string
minLength: 1
maxLength: 40
required:
- ids
- action
responses:
"200":
description: Per-id outcomes.
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentBulkEnvelope"
"401": *a1
"422": *a3
"429": *a4
/api/documents/inbound/{id}:
@@ -8845,6 +8977,7 @@ paths:
- LAB_RESULT
- IMAGING
- PRESCRIPTION
- REFERRAL
- INSURANCE
- VACCINATION
- OTHER
@@ -8853,6 +8986,13 @@ paths:
- type: string
pattern: ^\d{4}-\d{2}-\d{2}$
- type: "null"
episodeIds:
maxItems: 20
type: array
items:
type: string
minLength: 1
maxLength: 40
responses:
"200":
description: The updated document.
@@ -8885,16 +9025,14 @@ paths:
"401": *a1
"422": *a3
"429": *a4
/api/documents/inbound/{id}/original:
get:
/api/documents/inbound/{id}/restore:
post:
tags:
- 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. 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."
summary: Restore a discarded document
description: Clears the soft-delete tombstone within the 30-day undo grace. 409 when the tombstone is already purged
(the undo window is over) or when an identical live document blocks the restore (`meta.reason =
"duplicateExists"`). Restoring an already-live document is a no-op success.
parameters:
- name: id
in: path
@@ -8903,7 +9041,42 @@ paths:
type: string
responses:
"200":
description: The decrypted original document bytes with the stored MIME type.
description: The restored document + its staged facts.
content:
application/json:
schema:
$ref: "#/components/schemas/InboundRestoreEnvelope"
"401": *a1
"409":
description: "Not restorable: purged past the grace window, or an identical live document exists."
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/documents/inbound/{id}/original:
get:
tags:
- 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. Serving
posture follows the document's `servingClass`: inline types (PDF/JPEG/PNG/WebP/GIF) come with their true
Content-Type, `Content-Disposition: inline`, `nosniff`, and a `Content-Security-Policy: sandbox` header;
attachment types (Office/text/TIFF/HEIC/XML/JSON) ALWAYS come as `application/octet-stream` +
`Content-Disposition: attachment` + `nosniff` — never inline. 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
required: true
schema:
type: string
responses:
"200":
description: The decrypted original document bytes. Inline types carry the stored MIME type; attachment types are served
as application/octet-stream.
content:
application/pdf:
schema:
@@ -8913,6 +9086,10 @@ paths:
schema:
type: string
format: binary
application/octet-stream:
schema:
type: string
format: binary
"401": *a1
"422": *a3
"429": *a4
@@ -10698,6 +10875,8 @@ components:
type: boolean
mcp:
type: boolean
inboundDocuments:
type: boolean
mentalHealth:
type: boolean
additionalProperties: false
@@ -11256,6 +11435,7 @@ components:
- LAB_RESULT
- IMAGING
- PRESCRIPTION
- REFERRAL
- INSURANCE
- VACCINATION
- OTHER
@@ -25699,6 +25879,7 @@ components:
- LAB_RESULT
- IMAGING
- PRESCRIPTION
- REFERRAL
- INSURANCE
- VACCINATION
- OTHER
@@ -25743,6 +25924,15 @@ components:
type: number
pendingCount:
type: number
conditionLinks:
type: array
items:
$ref: "#/components/schemas/DocumentConditionLink"
servingClass:
type: string
enum:
- inline
- attachment
createdAt:
type: string
updatedAt:
@@ -25761,13 +25951,29 @@ components:
- errorReason
- factCount
- pendingCount
- conditionLinks
- servingClass
- createdAt
- updatedAt
additionalProperties: false
description: A stored document. The raw bytes are stored encrypted at rest and never returned; this is the metadata +
staging summary. `status` is STORED for a freshly uploaded file (no extraction run). `title` is the user label
(plaintext); `documentDate` is the user filing date; `reportDate` is the model-transcribed date (null until
extraction runs).
extraction runs). `servingClass` says how `/original` delivers the file — render inline (`inline`) or
download-only (`attachment`); render/download by it, never by MIME guess. Treat an unknown `kind` value as OTHER
when decoding.
DocumentConditionLink:
type: object
properties:
episodeId:
type: string
name:
type: string
required:
- episodeId
- name
additionalProperties: false
description: A link to one of the caller's illness/condition episodes. `name` is the episode's user-facing label.
InboundDocumentEnvelope:
type: object
properties:
@@ -25785,6 +25991,97 @@ components:
- data
- error
additionalProperties: false
DocumentUsageEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/DocumentUsage"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
DocumentUsage:
type: object
properties:
usedBytes:
type: number
quotaBytes:
type: number
maxFileBytes:
type: number
acceptedExtensions:
type: array
items:
type: string
linkedEpisodes:
type: array
items:
type: object
properties:
episodeId:
type: string
name:
type: string
required:
- episodeId
- name
additionalProperties: false
required:
- usedBytes
- quotaBytes
- maxFileBytes
- acceptedExtensions
- linkedEpisodes
additionalProperties: false
DocumentBulkEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/DocumentBulkResponse"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
DocumentBulkResponse:
type: object
properties:
results:
type: array
items:
type: object
properties:
id:
type: string
ok:
type: boolean
error:
anyOf:
- type: string
- type: "null"
required:
- id
- ok
- error
additionalProperties: false
required:
- results
additionalProperties: false
InboundDocumentDetailEnvelope:
type: object
properties:
@@ -25815,6 +26112,7 @@ components:
- LAB_RESULT
- IMAGING
- PRESCRIPTION
- REFERRAL
- INSURANCE
- VACCINATION
- OTHER
@@ -25859,6 +26157,15 @@ components:
type: number
pendingCount:
type: number
conditionLinks:
type: array
items:
$ref: "#/components/schemas/DocumentConditionLink"
servingClass:
type: string
enum:
- inline
- attachment
createdAt:
type: string
updatedAt:
@@ -25881,6 +26188,8 @@ components:
- errorReason
- factCount
- pendingCount
- conditionLinks
- servingClass
- createdAt
- updatedAt
- facts
@@ -25993,6 +26302,23 @@ components:
- data
- error
additionalProperties: false
InboundRestoreEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/InboundDocumentDetail"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
InboundExtractEnvelope:
type: object
properties: