feat(documents): persist an auto summary for uploaded documents

When the auto-AI-read opt-in is on, a freshly uploaded document is summarised
once in the background and the encrypted summary is stored and shown on the
document detail view.

- A background step runs after upload alongside the existing content-index and
  thumbnail steps. It generates a short (3-4 sentence) plain-language summary
  only when the user's documentsAutoAiRead opt-in is on (that flag is also the
  standing AI-egress consent), skips a document that already has one, and
  no-ops when no vision provider is configured. It reuses the same provider
  resolution, egress-consent re-assertion, and AI-budget reserve/reconcile
  helpers as the on-demand route; a provider error refunds the reservation and
  never fails the upload.
- The summary is stored encrypted (summaryEncrypted Bytes) with a
  summaryGeneratedAt timestamp on inbound_documents, registered for key
  rotation, and returned decrypted in the document detail response.
- The on-demand transient summary route is unchanged.

Gated by the same consent, budget, and module checks.
This commit is contained in:
Marc-André Bombeck committed 2026-07-17 17:46:28 +02:00
1 parent 43eddb63b0
commit 5cff7473c9
19 files changed
+674 -22

No files matched your search

+15
View File
@@ -29085,6 +29085,14 @@ components:
type: array
items:
$ref: "#/components/schemas/ExtractedFact"
summary:
anyOf:
- type: string
- type: "null"
summaryGeneratedAt:
anyOf:
- type: string
- type: "null"
required:
- id
- kind
@@ -29107,7 +29115,14 @@ components:
- createdAt
- updatedAt
- facts
- summary
- summaryGeneratedAt
additionalProperties: false
description: A stored document plus its staged facts. `summary` is a short (3-4 sentence) plain-language description of
WHAT the document is, generated once in the background right after upload when the `documentsAutoAiRead` opt-in
is ON (that flag is also the standing AI-egress consent); it is descriptive only, never a diagnosis, and is null
when auto-read is OFF, no provider is configured, or the background summary has not run yet.
`summaryGeneratedAt` is when it was generated (null until then).
ExtractedFact:
type: object
properties: