Merge the release with the security review fixes into the data-safety review fixes

This commit is contained in:
Marc-André Bombeck committed 2026-10-25 18:36:54 +01:00
commit bcb31c80db
98 files changed
+3166 -608

No files matched your search

+4 -2
View File
@@ -105,7 +105,9 @@ describe("demo seed, short-window shape", () => {
it("leaves the median of the window in the settled band", () => {
// The usual range is the median ± 3 MAD of the last 30 days. A stretch
// that fills half the window moves that median onto the stretch and
// widens the band to something like 87-181 mmHg.
// widens the band to something like 87-181 mmHg. The seed's own spread
// reaches about ±22 in one run of 150, so the bound sits at 24, still far
// below what a stretch that long produces.
const sorted = [...systolic].sort((a, b) => a - b);
const median = sorted[Math.floor(sorted.length / 2)];
expect(median).toBeLessThanOrEqual(124);
@@ -113,7 +115,7 @@ describe("demo seed, short-window shape", () => {
.map((v) => Math.abs(v - median))
.sort((a, b) => a - b);
const mad = deviations[Math.floor(deviations.length / 2)];
expect(3 * 1.4826 * mad).toBeLessThanOrEqual(20);
expect(3 * 1.4826 * mad).toBeLessThanOrEqual(24);
});
it("raises ramipril on the day the stretch turns", () => {
@@ -540,6 +540,68 @@ describe("the reaper", () => {
});
});
/**
* A run's deadline is model time. The document reader moves it by the time
* a read waited for the reader and took, so a person's lab scan is not
* timed out because other work held the reader; never past the job's own
* expiry, and only while the run is running.
*/
describe("the deadline while the document reader works", () => {
async function runningRun(expiresAt: Date) {
const user = await makeUser();
await grantConsent(user.id);
await signIn(user.id);
const queued = await postLabScanText("Glucose 95 mg/dL");
const { runId } = queued.body.data as { runId: string };
await getPrismaClient().documentAiRun.update({
where: { id: runId },
data: { status: "RUNNING", expiresAt },
});
return runId;
}
const expiry = async (runId: string) =>
(
await getPrismaClient().documentAiRun.findUniqueOrThrow({
where: { id: runId },
})
).expiresAt.getTime();
it("moves a running run's deadline by the reader's time, so the reaper leaves it alone", async () => {
const { moveRunDeadline } = await import("@/lib/jobs/document-ai-run");
const { reapAiRuns } = await import("@/lib/documents/ai-runs/store");
const armed = new Date(Date.now() + 5_000);
const runId = await runningRun(armed);
// The slot is granted after a 60 s wait, with 70 s allowed for the read.
await moveRunDeadline(runId, 130_000, undefined);
expect(await expiry(runId)).toBe(armed.getTime() + 130_000);
// Without the move the reaper would have taken it a minute later.
expect(
(await reapAiRuns(new Date(armed.getTime() + 60_000))).timedOut,
).toBe(0);
// The read took 10 s of its 70: the unused 60 s go back.
await moveRunDeadline(runId, -60_000, undefined);
expect(await expiry(runId)).toBe(armed.getTime() + 70_000);
});
it("never moves it past the job's own expiry, and leaves a run that is not running alone", async () => {
const { moveRunDeadline } = await import("@/lib/jobs/document-ai-run");
const armed = new Date(Date.now() + 5_000);
const runId = await runningRun(armed);
const jobEnds = armed.getTime() + 20_000;
await moveRunDeadline(runId, 130_000, jobEnds);
expect(await expiry(runId)).toBe(jobEnds);
await getPrismaClient().documentAiRun.update({
where: { id: runId },
data: { status: "FAILED" },
});
await moveRunDeadline(runId, 130_000, undefined);
expect(await expiry(runId)).toBe(jobEnds);
});
});
describe("a summary in the background", () => {
it("marks a stored summary PENDING while queued and READY when it lands", async () => {
const user = await makeUser();
@@ -22,7 +22,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { GET } from "@/app/api/auth/oidc/callback/route";
import { encrypt } from "@/lib/crypto";
import { OIDC_STATE_COOKIE } from "@/lib/auth/oidc-cookie";
import { SESSION_COOKIE_NAME } from "@/lib/auth/session";
import { sessionCookieName } from "@/lib/auth/session-cookie";
import { anonymous, callRoute } from "../../../kit/call-route";
import { makeSession, makeUser } from "../../../kit/factories";
@@ -312,7 +312,7 @@ describe("GET /api/auth/oidc/callback parity", () => {
const session = await makeSession(first);
await step("12-duplicate-with-session", {
state: null,
cookies: { [SESSION_COOKIE_NAME]: session.cookie },
cookies: { [sessionCookieName()]: session.cookie },
});
});
@@ -382,7 +382,7 @@ describe("GET /api/auth/oidc/callback parity", () => {
identity("sub-claimer", "claimer@example.test", true);
await step("23-claim-signed-in", {
state: { intent: "claim", claimBinding: "binding-1" },
cookies: { [SESSION_COOKIE_NAME]: session.cookie },
cookies: { [sessionCookieName()]: session.cookie },
});
await step("24-claim-no-cookie", {
state: { intent: "claim", claimBinding: "binding-1" },
+3 -3
View File
@@ -25,7 +25,7 @@ import { NextRequest } from "next/server";
import type { z } from "zod";
import { openApiComponents, openApiPaths } from "@/lib/openapi/routes";
import { SESSION_COOKIE_NAME } from "@/lib/auth/session";
import { sessionCookieName } from "@/lib/auth/session-cookie";
import { cookieJar, headerJar } from "../integration/mock-next-headers";
import type { BearerFixture, SessionFixture } from "./factories";
@@ -122,7 +122,7 @@ export async function callRoute<T = unknown>(
headers.set("authorization", `Bearer ${options.auth.token}`);
}
if (options.auth.kind === "session") {
headers.set("cookie", `${SESSION_COOKIE_NAME}=${options.auth.cookie}`);
headers.set("cookie", `${sessionCookieName()}=${options.auth.cookie}`);
}
// `next/headers` is served from the shared jars; fill them for this call
@@ -133,7 +133,7 @@ export async function callRoute<T = unknown>(
const savedHeaders = new Map(headerJar);
cookieJar.clear();
if (options.auth.kind === "session") {
cookieJar.set(SESSION_COOKIE_NAME, options.auth.cookie);
cookieJar.set(sessionCookieName(), options.auth.cookie);
}
headerJar.delete("authorization");
headers.forEach((value, name) => headerJar.set(name.toLowerCase(), value));
+3 -2
View File
@@ -27,7 +27,8 @@ import type {
Workout,
} from "@/generated/prisma/client";
import { prisma } from "@/lib/db";
import { createSession, SESSION_COOKIE_NAME } from "@/lib/auth/session";
import { createSession } from "@/lib/auth/session";
import { sessionCookieName } from "@/lib/auth/session-cookie";
import { issueApiToken } from "@/lib/auth/issue-token";
import { cookieJar } from "../integration/mock-next-headers";
@@ -91,7 +92,7 @@ export async function makeSession(
"healthlog-kit",
options.mfaVerifiedAt ?? null,
);
const cookie = cookieJar.get(SESSION_COOKIE_NAME);
const cookie = cookieJar.get(sessionCookieName());
cookieJar.clear();
for (const [name, value] of before) cookieJar.set(name, value);
if (!cookie) {