fix(medications): end every medication and intake read on the row id

Postgres returns rows that tie on an ORDER BY in no particular order,
and not the same one twice. The medication reads ordered by columns two
rows share all the time: every read that hands schedules on included
them with no order at all, medications were listed by createdAt alone
(one import writes many in a millisecond), intakes by scheduledFor (a
twice-daily medication puts every morning dose on one instant), and
"the latest intake" lookups by takenAt. A client read the same account
in a different order from one request to the next, and a latest-intake
lookup picked either of two rows.

Every findMany / findFirst on a medication table, nested includes
included, now ends its order on the id in the direction of its last key,
and schedules are read by window, then id (MEDICATION_SCHEDULE_ORDER, the
order the backup already wrote). medication-read-order-guard.test.ts
holds the tree to it. medication-read-order.test.ts writes tying rows in
the opposite order to the one each read promises, through the medication
list and today's intakes, and fails without the tie-breakers.

The parity suite's own state reader had the same gap: it sorts rows by
content with the ids blanked, and rows that tie then kept whatever order
the unordered read returned, so their <id:N> labels swapped under a
loaded run. Its reads are ordered by id, which for a cuid is the order
the rows were written in.

The JSON import's schema left the route, so its entry in the inline
schema strictness register goes; the schema module says why its objects
are not strict.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 21:54:25 +02:00
1 parent 7936ab9a57
commit 111047491e
57 files changed
+476 -142

No files matched your search

@@ -1,7 +1,7 @@
{
"$comment": "Production files over 800 lines and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 89,
"total": 99600,
"total": 99598,
"entries": {
"app/medications/page-client.tsx": 839,
"components/charts/mood-chart.tsx": 1166,
@@ -72,7 +72,7 @@
"lib/jobs/medication-intake-import.ts": 819,
"lib/jobs/offhost-backup.ts": 1147,
"lib/jobs/reaction-line.ts": 824,
"lib/jobs/reminder/medication-reminder-check.ts": 818,
"lib/jobs/reminder/medication-reminder-check.ts": 816,
"lib/managed-profiles/handover.ts": 986,
"lib/mcp/record-search.ts": 1467,
"lib/mcp/rich-reads.ts": 1467,
@@ -1,7 +1,7 @@
{
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 354,
"total": 114417,
"total": 114410,
"entries": {
"app/achievements/page.tsx :: AchievementsPage": 206,
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314,
@@ -319,8 +319,8 @@
"lib/jobs/medication-low-stock.ts :: runMedicationLowStockTick": 286,
"lib/jobs/oura-backfill.ts :: runOuraBackfillForUser": 236,
"lib/jobs/reaction-line.ts :: runReactionLine": 337,
"lib/jobs/reminder/medication-reminder-check.ts :: handleReminderCheck": 619,
"lib/jobs/reminder/medication-reminder-check.ts :: handleReminderCheck > withBackgroundEvent(arg)": 614,
"lib/jobs/reminder/medication-reminder-check.ts :: handleReminderCheck": 616,
"lib/jobs/reminder/medication-reminder-check.ts :: handleReminderCheck > withBackgroundEvent(arg)": 611,
"lib/jobs/workout-insight-generate.ts :: runWorkoutInsightGenerate": 324,
"lib/labs/biomarker-merge.ts :: runMerge": 254,
"lib/labs/ocr-extract.ts :: runOcrExtraction": 207,
@@ -337,7 +337,7 @@
"lib/measurements/import-apple-health-export.ts :: streamParseExportXml > (anonymous) #2": 323,
"lib/medications/efficacy/build-efficacy.ts :: buildMedicationEfficacy": 268,
"lib/medications/intake-slot-dedup.ts :: dedupeUserIntakeSlots": 321,
"lib/medications/list-read.ts :: buildMedicationsList": 310,
"lib/medications/list-read.ts :: buildMedicationsList": 309,
"lib/medications/scheduling/dose-history.ts :: reconstructDoseHistory": 218,
"lib/multipart/stream-to-disk.ts :: streamMultipartToDisk": 272,
"lib/notifications/dispatcher.ts :: dispatchNotification": 378,
@@ -1,7 +1,7 @@
{
"$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.",
"count": 2897,
"total": 9290,
"total": 9285,
"entries": {
"__tests__/account-payload-consumer-guard.test.ts": 2,
"__tests__/acting-account-boundary-guard.test.ts": 7,
@@ -2357,7 +2357,7 @@
"lib/jobs/reminder/google-health-sync.ts": 1,
"lib/jobs/reminder/insights-handlers.ts": 3,
"lib/jobs/reminder/medication-maintenance.ts": 3,
"lib/jobs/reminder/medication-reminder-check.ts": 13,
"lib/jobs/reminder/medication-reminder-check.ts": 12,
"lib/jobs/reminder/mood-cycle-checks.ts": 3,
"lib/jobs/reminder/ops-handlers.ts": 3,
"lib/jobs/reminder/oura-sync.ts": 1,
@@ -2506,7 +2506,7 @@
"lib/medications/injection-site-write.ts": 1,
"lib/medications/injection-sites.ts": 6,
"lib/medications/intake-actionable.ts": 1,
"lib/medications/intake-slot-dedup.ts": 18,
"lib/medications/intake-slot-dedup.ts": 17,
"lib/medications/intake-tracking.ts": 1,
"lib/medications/inventory/__tests__/consumption.test.ts": 1,
"lib/medications/inventory/__tests__/summary.test.ts": 2,
@@ -2515,7 +2515,7 @@
"lib/medications/inventory/state-machine.ts": 2,
"lib/medications/inventory/summary.ts": 4,
"lib/medications/lifecycle.ts": 3,
"lib/medications/list-read.ts": 26,
"lib/medications/list-read.ts": 23,
"lib/medications/med-target-map.ts": 1,
"lib/medications/medication-order.ts": 1,
"lib/medications/outstanding-doses.ts": 2,
@@ -0,0 +1,139 @@
/**
* Every medication and intake read orders its ties.
*
* Postgres returns rows that tie on an `ORDER BY` in no particular order, and
* not the same one twice. The medication reads ordered by columns two rows
* share all the time: a schedule list with no order at all, medications by
* `createdAt` (one import writes many in a millisecond), intakes by
* `scheduledFor` (a twice-daily medication puts every morning dose on the same
* instant), "the latest intake" by `takenAt`. A client then read the same
* account in a different order from one request to the next, and a "latest"
* lookup picked either of two rows. The parity suite saw it as `<id:N>`
* labels swapping under a loaded run.
*
* So every `findMany` / `findFirst` on a medication table, its nested
* includes too, states an order that ends on a unique key, and a medication's
* schedules are never included unordered (`MEDICATION_SCHEDULE_ORDER`). An
* order held in a variable is checked where it is built, and listed below.
*/
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import {
readSourceText,
requireFloor,
stripComments,
walkSourceFiles,
} from "./helpers/source-files";
const SRC = join(__dirname, "..");
const READ =
/\b(?:prisma|tx|db|client)\s*\.\s*(medication|medicationSchedule|medicationIntakeEvent|medicationScheduleRevision|medicationPauseEra|medicationCourse|medicationDoseChange|medicationInventoryItem|medicationInventoryEvent|medicationSideEffect|medicationIngredient|medicationCategoryAssignment)\s*\.\s*(findMany|findFirst|findFirstOrThrow)\s*\(/g;
/** Unique keys an order may end on, beyond `id`. */
const UNIQUE_LAST_KEYS: Record<string, string> = {
medicationCategoryAssignment: "medicationId",
};
/** Orders built in a variable, by file, with where the tie-breaker lives. */
const ORDER_VARIABLES: Record<string, string> = {
"lib/medications/list-intake-events.ts":
"`orderBy` ends on `{ id: sortDir }` in both arms, built just above the read.",
};
function closing(text: string, at: number, open: string, close: string) {
let depth = 0;
for (let i = at; i < text.length; i++) {
if (text[i] === open) depth++;
else if (text[i] === close && --depth === 0) return i;
}
return -1;
}
interface Read {
where: string;
model: string;
orders: string[];
variableOrder: boolean;
unorderedSchedules: boolean;
}
function reads(): Read[] {
const out: Read[] = [];
const files = walkSourceFiles(SRC, { floor: 2000 }).filter(
(rel) => !rel.startsWith("generated/") && !rel.includes("__tests__"),
);
for (const rel of files) {
const text = stripComments(readSourceText(join(SRC, rel)), rel);
for (const match of text.matchAll(READ)) {
const open = match.index! + match[0].length - 1;
const end = closing(text, open, "(", ")");
const call = text.slice(open, end + 1);
const orders: string[] = [];
for (const order of call.matchAll(/orderBy\s*:\s*([[{])/g)) {
const start = order.index! + order[0].length - 1;
const bracket = order[1]!;
const stop = closing(call, start, bracket, bracket === "[" ? "]" : "}");
orders.push(call.slice(start, stop + 1).replace(/\s+/g, " "));
}
out.push({
where: `${rel}:${text.slice(0, match.index).split("\n").length}`,
model: match[1]!,
orders,
variableOrder: /\borderBy\s*(?:,|\})/.test(call),
unorderedSchedules: /\bschedules\s*:\s*true\b/.test(call),
});
}
}
return out;
}
const ALL = reads();
/** Whether an order's last key is unique for the read's model. */
function endsUnique(order: string, model: string): boolean {
const keys = [...order.matchAll(/(\w+)\s*:\s*(?:"(?:asc|desc)"|\{|\w)/g)]
.map((m) => m[1]!)
.filter((key) => key !== "sort" && key !== "nulls");
const last = keys.at(-1);
return last === "id" || last === UNIQUE_LAST_KEYS[model];
}
describe("medication reads order their ties", () => {
it("finds the reads it is about", () => {
requireFloor("medication reads", ALL, 150);
requireFloor(
"ordered medication reads",
ALL.filter((read) => read.orders.length > 0),
60,
);
});
it("ends every stated order on a unique key", () => {
const loose = ALL.flatMap((read) =>
read.orders
.filter((order) => !endsUnique(order, read.model))
.map((order) => `${read.where} ${read.model}: ${order}`),
);
expect(loose).toEqual([]);
});
it("never includes a medication's schedules unordered", () => {
expect(
ALL.filter((read) => read.unorderedSchedules).map((read) => read.where),
).toEqual([]);
});
it("names every order held in a variable, and nothing else", () => {
const files = [
...new Set(
ALL.filter((read) => read.variableOrder).map(
(read) => read.where.split(":")[0]!,
),
),
].sort();
expect(files).toEqual(Object.keys(ORDER_VARIABLES).sort());
});
});