From 6ec2d499fb709c5951ea3269d66e226423ad0bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Mon, 18 May 2026 12:57:25 +0200 Subject: [PATCH] fix(medication-compliance): scope coverage-miss backfill enqueue to caller user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QA F-SEC-M-01 (v1.4.39): the route's coverage-miss fallback was firing `enqueueBootTimeMedicationComplianceBackfill` on every authenticated hit, running a cluster-wide `LEFT JOIN medication_intake_events x medication_compliance_rollups` discovery scan in the request path. On a multi-tenant cluster with millions of intake events the planner walked all of them per request — a soft DoS amplifier. Split into two helpers: `enqueueUserMedicationComplianceBackfill(userId)` for the request path (one targeted enqueue, no discovery scan) and the existing cluster-wide discovery stays for the worker-boot path. Mirrors the mood-rollup `ensureUserMoodRollupsFresh` posture. Pinned with a unit test that asserts the user-scoped helper never issues `$queryRaw`. --- src/app/api/medications/intake/route.ts | 16 +++++-- .../__tests__/compliance-rollups.test.ts | 39 +++++++++++++++ src/lib/medications/compliance-rollups.ts | 47 +++++++++++++++++++ 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/app/api/medications/intake/route.ts b/src/app/api/medications/intake/route.ts index 3951e5e65..d290166d9 100644 --- a/src/app/api/medications/intake/route.ts +++ b/src/app/api/medications/intake/route.ts @@ -30,7 +30,7 @@ import { readMedicationCompliance, hasMedicationComplianceCoverage, recomputeMedicationComplianceForEvent, - enqueueBootTimeMedicationComplianceBackfill, + enqueueUserMedicationComplianceBackfill, type ComplianceBucket as RollupComplianceBucket, } from "@/lib/medications/compliance-rollups"; @@ -169,10 +169,16 @@ async function readComplianceBucketsWithFallback( } // Coverage miss — the legacy aggregator stays correct over an empty - // rollup window. Fire the boot backfill in the background so the - // next request lands on the rollup tier; the current request still - // returns the live-derived buckets. - void enqueueBootTimeMedicationComplianceBackfill(); + // rollup window. Fire a user-scoped backfill in the background so + // the next request lands on the rollup tier; the current request + // still returns the live-derived buckets. + // + // QA F-SEC-M-01 (v1.4.39): pre-fix this fired the cluster-wide + // `enqueueBootTimeMedicationComplianceBackfill` on every coverage- + // miss request, opening a soft-DoS amplifier (every authenticated + // user could drive a multi-tenant `LEFT JOIN` scan on each hit). + // The user-scoped helper enqueues exactly the caller's account. + void enqueueUserMedicationComplianceBackfill(userId); annotate({ meta: { medication_compliance_path: "live-fallback", diff --git a/src/lib/medications/__tests__/compliance-rollups.test.ts b/src/lib/medications/__tests__/compliance-rollups.test.ts index effee8ef7..e3032094d 100644 --- a/src/lib/medications/__tests__/compliance-rollups.test.ts +++ b/src/lib/medications/__tests__/compliance-rollups.test.ts @@ -54,6 +54,7 @@ import { hasMedicationComplianceCoverage, recomputeUserMedicationCompliance, enqueueBootTimeMedicationComplianceBackfill, + enqueueUserMedicationComplianceBackfill, MEDICATION_COMPLIANCE_BACKFILL_QUEUE, } from "../compliance-rollups"; @@ -434,3 +435,41 @@ describe("enqueueBootTimeMedicationComplianceBackfill", () => { ); }); }); + +describe("enqueueUserMedicationComplianceBackfill", () => { + it("is a no-op when no boss is attached", async () => { + getGlobalBossMock.mockReturnValue(null); + const result = await enqueueUserMedicationComplianceBackfill("user-1"); + expect(result).toEqual({ enqueued: false, error: null }); + // Critically: no `$queryRaw` cluster-wide LEFT JOIN scan. + expect(queryRaw).not.toHaveBeenCalled(); + }); + + it("enqueues exactly one job for the caller's user and never touches the cluster-wide LEFT JOIN", async () => { + // QA F-SEC-M-01 (v1.4.39): pre-fix the request-path coverage-miss + // path called `enqueueBootTimeMedicationComplianceBackfill` which + // issued a cluster-wide `LEFT JOIN medication_intake_events × + // medication_compliance_rollups` on every hit. The user-scoped + // helper sends one targeted job and never runs the discovery scan. + getGlobalBossMock.mockReturnValue({ send: bossSend }); + bossSend.mockResolvedValueOnce("job-1"); + const result = await enqueueUserMedicationComplianceBackfill("user-1"); + expect(result).toEqual({ enqueued: true, error: null }); + expect(queryRaw).not.toHaveBeenCalled(); + expect(bossSend).toHaveBeenCalledTimes(1); + expect(bossSend).toHaveBeenCalledWith( + MEDICATION_COMPLIANCE_BACKFILL_QUEUE, + expect.objectContaining({ userId: "user-1" }), + expect.objectContaining({ + singletonKey: "medication-compliance-boot-backfill|user-1", + }), + ); + }); + + it("reports a coalesced enqueue as `enqueued:false`", async () => { + getGlobalBossMock.mockReturnValue({ send: bossSend }); + bossSend.mockResolvedValueOnce(null); + const result = await enqueueUserMedicationComplianceBackfill("user-2"); + expect(result).toEqual({ enqueued: false, error: null }); + }); +}); diff --git a/src/lib/medications/compliance-rollups.ts b/src/lib/medications/compliance-rollups.ts index 27b5ab4c4..9f7287c69 100644 --- a/src/lib/medications/compliance-rollups.ts +++ b/src/lib/medications/compliance-rollups.ts @@ -516,6 +516,53 @@ export async function ensureUserMedicationComplianceFresh( } } +/** + * v1.4.39 hotfix (QA F-SEC-M-01) — user-scoped enqueue helper for the + * request-path coverage-miss fallback. + * + * Mirrors `ensureUserMoodRollupsFresh`: the route fires this when its + * coverage probe returned false so the caller's account picks up a + * targeted boot-backfill job, instead of running a cluster-wide + * `LEFT JOIN` over `medication_intake_events × medication_compliance_rollups` + * on every authenticated coverage-miss request. The cluster-wide scan + * was a soft DoS amplifier — an authenticated user iterating + * coverage-miss requests could drive a multi-tenant table scan on + * every hit. + * + * The boot-time discovery helper below stays cluster-wide (it runs + * once per worker boot, not per request). + */ +export async function enqueueUserMedicationComplianceBackfill( + userId: string, +): Promise<{ enqueued: boolean; error: string | null }> { + const boss = getGlobalBoss(); + if (!boss) { + return { enqueued: false, error: null }; + } + try { + const payload: MedicationComplianceBackfillPayload = { + userId, + enqueuedAt: new Date().toISOString(), + }; + const jobId = await boss.send( + MEDICATION_COMPLIANCE_BACKFILL_QUEUE, + payload, + { + retryLimit: 3, + retryDelay: 60, + retryBackoff: true, + singletonKey: `medication-compliance-boot-backfill|${userId}`, + }, + ); + return { enqueued: jobId !== null && jobId !== undefined, error: null }; + } catch (err) { + return { + enqueued: false, + error: err instanceof Error ? err.message : String(err), + }; + } +} + /** * Boot-time enqueue helper — mirrors v1.4.35.1 * `enqueueBootTimeRollupBackfill`. Finds users with intake events but