feat(documents): gate document, lab and medication reading on their capabilities

Reading a document, scanning a lab report and turning a medication
description into a schedule follow documentAi, labsOcr and
medicationExtract. Unavailable, the action is not offered. With only the
document-reading consent missing, the place that reads asks for it and
grants exactly that (ai_extraction), nothing wider.

Refusals are read by meta.errorCode: a 403 is no longer always called a
missing consent, and a spent daily budget no longer shows the rate-limit
sentence.
This commit is contained in:
Marc-André Bombeck committed 2026-09-24 11:05:49 +02:00
1 parent 48d97ef70d
commit 4449b34ac7
15 files changed
+466 -40

No files matched your search

+7
View File
@@ -16,6 +16,7 @@ import {
import { LabForm } from "@/components/labs/lab-form";
import { LabList } from "@/components/labs/lab-list";
import { useAiCapability } from "@/hooks/use-ai-capability";
import { OcrReviewDialog } from "@/components/labs/ocr-review-dialog";
import {
shouldProbeOcrCapability,
@@ -64,6 +65,10 @@ export default function LabsPage() {
// explicit `false`. Every `/api/labs/*` route also enforces the gate
// server-side, so this is a UX redirect, not the security boundary.
const enabled = user?.modules?.labs !== false;
// Scanning a lab report follows the `labsOcr` capability on
// `/api/auth/me`; with only the document-reading consent missing, the scan
// stays offered and the dialog asks for it.
const labsOcr = useAiCapability("labsOcr");
const ocrCapability = useOcrCapability(
shouldProbeOcrCapability({
isAuthenticated,
@@ -71,6 +76,7 @@ export default function LabsPage() {
labsEnabled: enabled,
mounted,
ownRecord: !inSharedRecord,
labsOcr,
}),
);
@@ -216,6 +222,7 @@ export default function LabsPage() {
onOpenChange={setScanOpen}
mode={ocrCapability.data.mode}
pdfSupported={ocrCapability.data.pdfSupported}
consentRequired={labsOcr.reason === "consent_required"}
onCommitted={() => {
queryClient.invalidateQueries({
queryKey: queryKeys.labResults(),