mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(ai): gate medication free-text extraction on active consent
The medication NL-extraction route egresses user-typed free text (PHI) to the operator's server-managed provider key; require an active consent receipt for the coach surface before the chain runs, matching the other server-managed egress sites.
This commit is contained in:
1 parent
3ce0b69802
commit
200cb5b996
2 files changed
+20
No files matched your search
@@ -43,6 +43,14 @@ vi.mock("@/lib/ai/provider", () => ({
|
||||
resolveProviderChain: vi.fn(async () => []),
|
||||
}));
|
||||
|
||||
// v1.12.1 — free-text medication extraction egresses PHI to the operator's
|
||||
// server-managed key, so it now passes through the consent gate. Mock it to a
|
||||
// no-op here; the gate's own fail-closed behaviour is covered by
|
||||
// `consent-guard.test.ts`.
|
||||
vi.mock("@/lib/ai/consent-guard", () => ({
|
||||
assertConsentForChain: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/ai/provider-runner", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("@/lib/ai/provider-runner")>(
|
||||
@@ -69,6 +77,7 @@ import {
|
||||
resolveProviderChain,
|
||||
} from "@/lib/ai/provider";
|
||||
import { runRawCompletionWithFallback } from "@/lib/ai/provider-runner";
|
||||
import { assertConsentForChain } from "@/lib/ai/consent-guard";
|
||||
|
||||
function postReq(body: unknown): NextRequest {
|
||||
return new NextRequest("http://localhost/api/medications/extract", {
|
||||
@@ -175,6 +184,11 @@ describe("POST /api/medications/extract — happy path", () => {
|
||||
doseUnit: "mg",
|
||||
cadenceKind: "everyNWeeks",
|
||||
});
|
||||
// The PHI free-text egress is gated on an active consent receipt for the
|
||||
// coach surface before the provider chain runs.
|
||||
expect(assertConsentForChain).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ surface: "coach" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("strips a hallucinated name that is absent from the user's text", async () => {
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
runRawCompletionWithFallback,
|
||||
} from "@/lib/ai/provider-runner";
|
||||
import { resolveProvider, resolveProviderChain } from "@/lib/ai/provider";
|
||||
import { assertConsentForChain } from "@/lib/ai/consent-guard";
|
||||
|
||||
import {
|
||||
buildDateKey,
|
||||
@@ -167,6 +168,11 @@ async function handleExtract(request: NextRequest): Promise<Response> {
|
||||
chain.push({ providerType: "admin-openai", instance: legacy });
|
||||
}
|
||||
|
||||
// Free-text medication input is PHI. If the resolved chain could egress
|
||||
// via the operator's server-managed key, an active consent receipt is
|
||||
// required first — same gate as the Coach (which shares this chain).
|
||||
await assertConsentForChain({ userId, chain, surface: "coach" });
|
||||
|
||||
const today = todayOverride ?? buildDateKey();
|
||||
const { systemPrompt, userPrompt } = buildMedicationExtractionPrompt({
|
||||
text,
|
||||
|
||||
Reference in new issue
Block a user