Files
HealthLog/tests/integration/sharing-manage-handler-matrix.test.ts
T

2921 lines
89 KiB
TypeScript

/**
* v1.37.0 — the MANAGE perimeter, driven as routes against real Postgres.
*
* MANAGE is the level where a delegate stops adding to somebody's record and
* starts changing it. Seventy modules answer at it; forty-three of them can
* destroy or rewrite something. The structural guard freezes WHICH routes say
* `"manage"` and, since this release, freezes the conditions each admission
* was granted on — but a frozen tag is a promise about code, not about
* behaviour, and the promises here are the kind that fail quietly: an audit
* row that names an id and nothing else, a rate bucket keyed on the wrong
* person, a navigation that spends the owner's AI budget.
*
* So this file drives the shipped exports. Same construction as
* `sharing-delegable-writes.test.ts`: the real `apiHandler`, the real
* `requireRecordAuth`, the real grant table, the real Prisma writes. The grant
* is minted through `inviteGrant` + `acceptGrant` because "a MANAGE grant
* exists" and "a MANAGE grant can be created by the flow this release ships"
* are different claims.
*
* Four legs per verb, then the conditions:
*
* 1. a manager with a live MANAGE grant changes the OWNER's record, and the
* audit row carries `userId = owner` AND `actorUserId = manager`;
* 2. the same person under a WRITE grant is refused, and nothing moved —
* asserted by re-reading the row, not by the status alone;
* 3. a caller who was never granted anything is refused the same way;
* 4. a caller who has not switched gets exactly what they always got.
*
* The conditions each get their own test, and each is written so that removing
* the implementation turns it red rather than leaving it green with a smaller
* claim: the reconstruction legs read the audit `details` and assert the
* VALUES, the scope leg reads the rate-limit table and asserts the key, and
* the generation leg asserts both halves — nothing enqueued for the manager,
* something enqueued for the owner doing the identical thing.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { NextRequest } from "next/server";
import { encryptToBytes } from "@/lib/ai/coach/bytes-codec";
import {
ADMITTED_MUTATING_HANDLERS,
type MutatingHandlerContract,
} from "../fixtures/v137/sharing-matrix";
import { cookieJar, headerJar } from "./mock-next-headers";
import { getPrismaClient, truncateAllTables, switchSessionTo } from "./setup";
vi.mock("next/headers", async () => {
const { cookieJar, headerJar } = await import("./mock-next-headers");
return {
headers: vi.fn(async () => ({
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
})),
cookies: vi.fn(async () => ({
get: (name: string) => {
const value = cookieJar.get(name);
return value ? { name, value } : undefined;
},
set: (name: string, value: string) => {
cookieJar.set(name, value);
},
delete: (name: string) => {
cookieJar.delete(name);
},
})),
};
});
vi.mock("@/lib/db-compat", () => ({
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
}));
// Import admissions have a durable database effect plus an asynchronous queue
// hand-off. Keep the hand-off deterministic here so each route test can prove
// the persisted job and actor audit without requiring a running worker.
vi.mock("@/lib/jobs/boss-instance", () => ({
getGlobalBoss: () => ({ send: vi.fn().mockResolvedValue("matrix-job") }),
}));
/**
* The narrative warm, stubbed at the seam the condition is about.
*
* C5 is the condition that costs the owner money when it is wrong, and what it
* suppresses is an ENQUEUE — a pg-boss insert whose absence is invisible from
* the response. Counting the calls is the only way to see it, and the count
* has to be taken on both sides: zero for the manager is worth nothing unless
* the same request by the owner is one.
*/
const enqueueNarrativeWarm = vi.fn(async (_payload: unknown) => {});
vi.mock("@/lib/jobs/period-narrative-shared", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/jobs/period-narrative-shared")>();
return {
...actual,
enqueueNarrativeWarm: (payload: unknown) => enqueueNarrativeWarm(payload),
};
});
let counter = 0;
async function makeUser(label: string) {
const suffix = `${label}-${counter++}`;
return getPrismaClient().user.create({
data: {
username: `mg-${suffix}`,
email: `mg-${suffix}@example.test`,
displayName: `MG ${suffix}`,
role: "USER",
timezone: "Europe/Berlin",
locale: "en",
},
});
}
async function signIn(userId: string) {
const session = await getPrismaClient().session.create({
data: { userId, expiresAt: new Date(Date.now() + 60_000) },
});
cookieJar.set("healthlog_session", session.id);
return session;
}
/** A live grant at the named level, accepted, the caller switched into it. */
async function switchInto(
ownerId: string,
delegateId: string,
access: "READ" | "WRITE" | "MANAGE",
) {
const { inviteGrant, acceptGrant } = await import("@/lib/sharing/grants");
const invited = await inviteGrant({
grantorId: ownerId,
granteeId: delegateId,
access,
scope: null,
});
const grant = await acceptGrant({
grantId: invited.id,
granteeId: delegateId,
});
expect(grant.access).toBe(access);
expect(grant.acceptedAt).not.toBeNull();
const session = await signIn(delegateId);
await switchSessionTo(session.id, ownerId);
return { grant, session };
}
/** A session inside a record nobody ever shared with this caller. */
async function claimWithoutGrant(ownerId: string, callerId: string) {
const session = await signIn(callerId);
await switchSessionTo(session.id, ownerId);
}
type Handler = (
request: NextRequest,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: { params: Promise<any> },
) => Promise<Response>;
function request(method: string, url: string, body?: unknown): NextRequest {
return new NextRequest(`http://localhost${url}`, {
method,
headers: { "content-type": "application/json" },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
}
async function call(
handler: Handler,
method: string,
url: string,
body?: unknown,
params: Record<string, string> = {},
): Promise<Response> {
return handler(request(method, url, body), {
params: Promise.resolve(params),
});
}
function matrixKey({ handlerModule, action, level }: MutatingHandlerContract) {
return `${handlerModule}:${action}:${level}`;
}
function admittedContract(
route: string,
action: MutatingHandlerContract["action"] = "POST",
) {
const match = ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === route && entry.action === action,
);
expect(
match,
`missing frozen admission for ${action} ${route}`,
).toBeDefined();
return match!;
}
async function matrixHandler(
contract: MutatingHandlerContract,
): Promise<Handler> {
const importer = ROUTE_IMPORTS[`/src/${contract.handlerModule}`];
expect(
importer,
`missing route import for ${contract.handlerModule}`,
).toBeTypeOf("function");
const route = await (importer as () => Promise<Record<string, unknown>>)();
const handler = route[contract.action];
expect(
handler,
`${contract.handlerModule} does not export ${contract.action}`,
).toBeTypeOf("function");
return handler as Handler;
}
async function driveAdmission(
contract: MutatingHandlerContract,
params: Record<string, string> = {},
body?: unknown,
): Promise<Response> {
let url: string = contract.route;
for (const [name, value] of Object.entries(params)) {
url = url.replace(`[${name}]`, value);
}
return call(
await matrixHandler(contract),
contract.action,
url,
body,
params,
);
}
async function payload(response: Response): Promise<{
data: unknown;
error: unknown;
meta?: { errorCode?: string };
}> {
return response.json();
}
/** The stable refusal, asserted by code rather than by status alone. */
async function expectDenied(response: Response) {
expect(response.status).toBe(403);
expect((await payload(response)).meta?.errorCode).toBe(
"sharing.access.denied",
);
}
/** The audit row a verb filed, with its details already parsed. */
async function auditRow(action: string) {
const row = await getPrismaClient().auditLog.findFirst({
where: { action },
orderBy: { createdAt: "desc" },
});
expect(row, `no ${action} row was written`).not.toBeNull();
return {
row: row!,
details: JSON.parse(row!.details ?? "{}") as Record<string, unknown>,
};
}
const ISO = "2026-07-15T08:00:00.000Z";
declare global {
interface ImportMeta {
glob<T>(pattern: string): Record<string, () => Promise<T>>;
}
}
/** Every registered driver must name one frozen admission exactly once. */
const STRICT_DRIVER_KEYS = new Set<string>();
/**
* Unlike the import-smoke inventory below, this set is filled only after a
* handler has changed an owned row and filed the delegated actor in audit.
* It is the executable answer to "did this route actually do its work?".
*/
const REAL_EFFECT_KEYS = new Set<string>();
const ROUTE_IMPORTS = import.meta.glob<Record<string, unknown>>(
"/src/app/api/**/route.ts",
);
describe("the complete MANAGE handler matrix", () => {
it("starts with a non-empty, exact handler inventory", () => {
expect(ADMITTED_MUTATING_HANDLERS.length).toBe(89);
});
});
/* -------------------------------------------------------------------------- */
/* The shape of a MANAGE case */
/* -------------------------------------------------------------------------- */
interface ManageCase<C> {
/** The frozen admission that this driver proves end to end. */
contract: MutatingHandlerContract;
/** Rows the verb acts on, created under the record it will change. */
prepare: (recordOwnerId: string) => Promise<C>;
/** Drive the shipped export against the current session. */
act: (ctx: C) => Promise<Response>;
/** The status a successful call answers with. */
ok: number;
/** The action the happy path files through `auditLog`. */
auditAction: string;
/** True once the verb has taken effect on the prepared rows. */
applied: (ctx: C) => Promise<boolean>;
}
function manageContract<C>(name: string, c: ManageCase<C>) {
STRICT_DRIVER_KEYS.add(matrixKey(c.contract));
describe(name, () => {
it("changes the owner's record and names the manager as actor", async () => {
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const ctx = await c.prepare(owner.id);
await switchInto(owner.id, manager.id, "MANAGE");
const response = await c.act(ctx);
expect(response.status).toBe(c.ok);
// The change landed…
expect(await c.applied(ctx)).toBe(true);
// …and the trail says who made it, under whose record.
const { row } = await auditRow(c.auditAction);
expect(row.userId).toBe(owner.id);
expect(row.actorUserId).toBe(manager.id);
REAL_EFFECT_KEYS.add(matrixKey(c.contract));
});
it("refuses a WRITE grant, and nothing moves", async () => {
const owner = await makeUser("owner");
const helper = await makeUser("helper");
const ctx = await c.prepare(owner.id);
await switchInto(owner.id, helper.id, "WRITE");
await expectDenied(await c.act(ctx));
// The mutation-check rule: assert it did NOT apply, by re-reading.
expect(await c.applied(ctx)).toBe(false);
});
it("refuses a caller who names a record they were never granted", async () => {
const owner = await makeUser("owner");
const stranger = await makeUser("stranger");
const ctx = await c.prepare(owner.id);
await claimWithoutGrant(owner.id, stranger.id);
await expectDenied(await c.act(ctx));
expect(await c.applied(ctx)).toBe(false);
});
it("is unchanged for a caller who has not switched", async () => {
const plain = await makeUser("plain");
const ctx = await c.prepare(plain.id);
await signIn(plain.id);
expect((await c.act(ctx)).status).toBe(c.ok);
expect(await c.applied(ctx)).toBe(true);
const { row } = await auditRow(c.auditAction);
expect(row.userId).toBe(plain.id);
expect(row.actorUserId).toBeNull();
});
});
}
/** The same proof shape for the small WRITE admission at the edge of MANAGE. */
function writeEffectContract<C>(name: string, c: ManageCase<C>) {
STRICT_DRIVER_KEYS.add(matrixKey(c.contract));
describe(name, () => {
it("keeps the owner's write effective", async () => {
const owner = await makeUser("owner");
const ctx = await c.prepare(owner.id);
await signIn(owner.id);
expect((await c.act(ctx)).status).toBe(c.ok);
expect(await c.applied(ctx)).toBe(true);
});
it("refuses the lower sharing level without applying it", async () => {
const owner = await makeUser("owner");
const delegate = await makeUser("denied");
const ctx = await c.prepare(owner.id);
await switchInto(owner.id, delegate.id, "READ");
await expectDenied(await c.act(ctx));
expect(await c.applied(ctx)).toBe(false);
});
it("writes for the owner and audits the WRITE actor", async () => {
const owner = await makeUser("owner");
const delegate = await makeUser("writer");
const ctx = await c.prepare(owner.id);
await switchInto(owner.id, delegate.id, "WRITE");
expect((await c.act(ctx)).status).toBe(c.ok);
expect(await c.applied(ctx)).toBe(true);
const { row } = await auditRow(c.auditAction);
expect(row.userId).toBe(owner.id);
expect(row.actorUserId).toBe(delegate.id);
REAL_EFFECT_KEYS.add(matrixKey(c.contract));
});
it("refuses an unsanctioned acting session without applying it", async () => {
const owner = await makeUser("owner");
const stranger = await makeUser("stranger");
const ctx = await c.prepare(owner.id);
await claimWithoutGrant(owner.id, stranger.id);
await expectDenied(await c.act(ctx));
expect(await c.applied(ctx)).toBe(false);
});
});
}
type RouteDriver<C> = Omit<ManageCase<C>, "contract" | "act"> & {
route: string;
action?: MutatingHandlerContract["action"];
params?: (ctx: C) => Record<string, string>;
body?: (ctx: C) => unknown;
};
function proveManageRoute<C>(name: string, driver: RouteDriver<C>) {
const contract = admittedContract(driver.route, driver.action);
manageContract(name, {
...driver,
contract,
act: (ctx) =>
driveAdmission(contract, driver.params?.(ctx), driver.body?.(ctx)),
});
}
function proveWriteRoute<C>(name: string, driver: RouteDriver<C>) {
const contract = admittedContract(driver.route, driver.action);
writeEffectContract(name, {
...driver,
contract,
act: (ctx) =>
driveAdmission(contract, driver.params?.(ctx), driver.body?.(ctx)),
});
}
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
cookieJar.clear();
headerJar.clear();
enqueueNarrativeWarm.mockClear();
});
/* -------------------------------------------------------------------------- */
/* Fixtures */
/* -------------------------------------------------------------------------- */
async function makeMeasurement(userId: string, value = 80) {
// The timestamp moves per row: `(userId, type, measuredAt, source,
// sleepStage)` is unique, so two fixtures on the same instant collide.
const measuredAt = new Date(Date.parse(ISO) + counter++ * 60_000);
return getPrismaClient().measurement.create({
data: {
userId,
type: "WEIGHT",
value,
unit: "kg",
measuredAt,
source: "MANUAL",
},
});
}
async function makeLabResult(userId: string) {
return getPrismaClient().labResult.create({
data: {
userId,
analyte: "HbA1c",
value: 5.2,
unit: "%",
takenAt: new Date(ISO),
},
});
}
async function makeAllergy(userId: string) {
return getPrismaClient().allergy.create({
data: {
userId,
substance: "Penicillin",
category: "MEDICATION",
type: "ALLERGY",
severity: "SEVERE",
status: "ACTIVE",
onsetAt: new Date("2020-03-01T00:00:00.000Z"),
},
});
}
async function makeReminder(userId: string) {
return getPrismaClient().measurementReminder.create({
data: {
userId,
label: "Colonoscopy recall",
measurementType: null,
intervalDays: 365,
notifyHour: 9,
nextDueAt: new Date("2027-03-01T08:00:00.000Z"),
enabled: true,
},
});
}
async function makeEpisode(userId: string) {
return getPrismaClient().illnessEpisode.create({
data: {
userId,
label: "Influenza",
type: "INFECTION",
onsetAt: new Date(ISO),
},
});
}
async function makePractitioner(userId: string) {
return getPrismaClient().practitioner.create({
data: { userId, name: "Praxis Nord", specialty: "Internal medicine" },
});
}
/**
* A visit that already happened.
*
* Past rather than booked on purpose: a PLANNED future visit mints an
* appointment reminder, and the drivers below assert one effect each. Keeping
* the fixture in the past leaves the edit and delete verbs saying only what
* they are here to say.
*/
async function makeVisit(userId: string) {
return getPrismaClient().encounter.create({
data: {
userId,
occurredAt: new Date(ISO),
status: "DONE",
kind: "ROUTINE",
},
});
}
/**
* One logged dose, catalogue-identified.
*
* `tetanus` rather than a combination on purpose: the drivers below assert one
* effect each, and a combination would satisfy several booster reminders at
* once if the fixture ever grew one. There is none here, so no reminder moves
* and the edit and delete verbs say only what they are here to say.
*/
async function makeVaccination(userId: string) {
return getPrismaClient().vaccinationRecord.create({
data: { userId, occurredAt: new Date(ISO), antigenSlug: "tetanus" },
});
}
async function makeCustomVaccine(userId: string) {
return getPrismaClient().customVaccine.create({
data: { userId, name: "Travel combo", components: ["typhoid"] },
});
}
async function makeFamilyHistory(userId: string) {
return getPrismaClient().familyHistoryEntry.create({
data: { userId, relationship: "MOTHER", condition: "Hypertension" },
});
}
async function makeMedication(userId: string, name = "Statin") {
return getPrismaClient().medication.create({
data: { userId, name, dose: "10mg" },
});
}
async function makeMedicationIntake(userId: string) {
const medication = await makeMedication(userId, "Insulin");
const event = await getPrismaClient().medicationIntakeEvent.create({
data: { userId, medicationId: medication.id, scheduledFor: new Date(ISO) },
});
return { medication, event };
}
async function makeInventoryItem(userId: string) {
const medication = await makeMedication(userId);
const item = await getPrismaClient().medicationInventoryItem.create({
data: {
userId,
medicationId: medication.id,
unitsTotal: 20,
unitsRemaining: 20,
},
});
return { medication, item };
}
async function makeScheduleRevision(userId: string) {
const medication = await makeMedication(userId);
const revision = await getPrismaClient().medicationScheduleRevision.create({
data: {
medicationId: medication.id,
validFrom: new Date(Date.now() - 3 * 86_400_000),
validUntil: new Date(Date.now() - 2 * 86_400_000),
source: "MANUAL",
payload: [{ timesOfDay: ["08:00"] }],
},
});
return { medication, revision };
}
/* -------------------------------------------------------------------------- */
/* 1 — the four legs, one verb per domain family */
/* -------------------------------------------------------------------------- */
manageContract("PUT /api/measurements/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/measurements/[id]" && entry.action === "PUT",
)!,
prepare: (ownerId) => makeMeasurement(ownerId),
act: async (row) => {
const { PUT } = await import("@/app/api/measurements/[id]/route");
return call(
PUT as Handler,
"PUT",
`/api/measurements/${row.id}`,
{
value: 74.5,
},
{ id: row.id },
);
},
ok: 200,
auditAction: "measurement.update",
applied: async (row) => {
const after = await getPrismaClient().measurement.findUnique({
where: { id: row.id },
});
return after?.value === 74.5;
},
});
manageContract("DELETE /api/measurements/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/measurements/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makeMeasurement(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/measurements/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/measurements/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "measurement.delete",
applied: async (row) => {
const after = await getPrismaClient().measurement.findUnique({
where: { id: row.id },
});
return after?.deletedAt !== null;
},
});
manageContract("PUT /api/labs/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/labs/[id]" && entry.action === "PUT",
)!,
prepare: (ownerId) => makeLabResult(ownerId),
act: async (row) => {
const { PUT } = await import("@/app/api/labs/[id]/route");
return call(
PUT as Handler,
"PUT",
`/api/labs/${row.id}`,
{ value: 6.1 },
{
id: row.id,
},
);
},
ok: 200,
auditAction: "labResult.update",
applied: async (row) => {
const after = await getPrismaClient().labResult.findUnique({
where: { id: row.id },
});
return after?.value === 6.1;
},
});
manageContract("PATCH /api/allergies/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/allergies/[id]" && entry.action === "PATCH",
)!,
prepare: (ownerId) => makeAllergy(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/allergies/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/allergies/${row.id}`,
{ severity: "MILD" },
{ id: row.id },
);
},
ok: 200,
auditAction: "allergy.update",
applied: async (row) => {
const after = await getPrismaClient().allergy.findUnique({
where: { id: row.id },
});
return after?.severity === "MILD";
},
});
manageContract("DELETE /api/measurement-reminders/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/measurement-reminders/[id]" &&
entry.action === "DELETE",
)!,
prepare: (ownerId) => makeReminder(ownerId),
act: async (row) => {
const { DELETE } =
await import("@/app/api/measurement-reminders/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/measurement-reminders/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "measurementReminder.delete",
applied: async (row) => {
const after = await getPrismaClient().measurementReminder.findUnique({
where: { id: row.id },
});
return after === null;
},
});
manageContract("POST /api/measurement-reminders/[id]/skip", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/measurement-reminders/[id]/skip" &&
entry.action === "POST",
)!,
prepare: (ownerId) => makeReminder(ownerId),
act: async (row) => {
const { POST } =
await import("@/app/api/measurement-reminders/[id]/skip/route");
return call(
POST as Handler,
"POST",
`/api/measurement-reminders/${row.id}/skip`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "measurementReminder.skip",
applied: async (row) => {
const after = await getPrismaClient().measurementReminder.findUnique({
where: { id: row.id },
});
const ledger = await getPrismaClient().measurementReminderEvent.count({
where: { reminderId: row.id, kind: "SKIPPED" },
});
// A skip stamps its own column and ledger row and never completes the
// reminder: lastSatisfiedAt must stay exactly as prepared (null).
return (
after?.lastSkippedAt !== null &&
after?.lastSatisfiedAt === null &&
ledger === 1
);
},
});
manageContract("POST /api/measurement-reminders/[id]/snooze", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/measurement-reminders/[id]/snooze" &&
entry.action === "POST",
)!,
prepare: (ownerId) => makeReminder(ownerId),
act: async (row) => {
const { POST } =
await import("@/app/api/measurement-reminders/[id]/snooze/route");
return call(
POST as Handler,
"POST",
`/api/measurement-reminders/${row.id}/snooze`,
// A snooze must end in the future; a fixed date stops being one.
{
until: new Date(Date.now() + 90 * 86_400_000)
.toISOString()
.slice(0, 10),
},
{ id: row.id },
);
},
ok: 200,
auditAction: "measurementReminder.snooze",
applied: async (row) => {
const after = await getPrismaClient().measurementReminder.findUnique({
where: { id: row.id },
});
return after?.snoozedUntil !== null && after?.lastSatisfiedAt === null;
},
});
/* -------------------------------------------------------------------------- */
/* 1a — measurements and their reminder schedules */
/* -------------------------------------------------------------------------- */
writeEffectContract("POST /api/measurements", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/measurements" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId, measuredAt: new Date(ISO) }),
act: async ({ measuredAt }) => {
const { POST } = await import("@/app/api/measurements/route");
return call(POST as Handler, "POST", "/api/measurements", {
type: "WEIGHT",
value: 81,
measuredAt: measuredAt.toISOString(),
});
},
ok: 201,
auditAction: "measurement.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().measurement.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("POST /api/measurements/bulk-delete", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/measurements/bulk-delete",
)!,
prepare: async (ownerId) => makeMeasurement(ownerId),
act: async (row) => {
const { POST } = await import("@/app/api/measurements/bulk-delete/route");
return call(POST as Handler, "POST", "/api/measurements/bulk-delete", {
ids: [row.id],
});
},
ok: 200,
auditAction: "measurement.delete.bulk",
applied: async (row) =>
(await getPrismaClient().measurement.findUnique({ where: { id: row.id } }))
?.deletedAt !== null,
});
manageContract("POST /api/measurements/restore", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/measurements/restore",
)!,
prepare: async (ownerId) => {
const row = await makeMeasurement(ownerId);
return getPrismaClient().measurement.update({
where: { id: row.id },
data: { deletedAt: new Date() },
});
},
act: async (row) => {
const { POST } = await import("@/app/api/measurements/restore/route");
return call(POST as Handler, "POST", "/api/measurements/restore", {
ids: [row.id],
});
},
ok: 200,
auditAction: "measurement.restore",
applied: async (row) =>
(await getPrismaClient().measurement.findUnique({ where: { id: row.id } }))
?.deletedAt === null,
});
manageContract("POST /api/measurement-reminders", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/measurement-reminders" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/measurement-reminders/route");
return call(POST as Handler, "POST", "/api/measurement-reminders", {
label: "Annual review",
intervalDays: 365,
notifyHour: 9,
enabled: true,
});
},
ok: 201,
auditAction: "measurementReminder.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().measurementReminder.count({
where: { userId: ownerId, label: "Annual review" },
})) === 1,
});
manageContract("PATCH /api/measurement-reminders/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/measurement-reminders/[id]" &&
entry.action === "PATCH",
)!,
prepare: (ownerId) => makeReminder(ownerId),
act: async (row) => {
const { PATCH } =
await import("@/app/api/measurement-reminders/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/measurement-reminders/${row.id}`,
{ label: "Annual review updated" },
{ id: row.id },
);
},
ok: 200,
auditAction: "measurementReminder.update",
applied: async (row) =>
(
await getPrismaClient().measurementReminder.findUnique({
where: { id: row.id },
})
)?.label === "Annual review updated",
});
manageContract("POST /api/measurement-reminders/[id]/complete", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/measurement-reminders/[id]/complete",
)!,
prepare: (ownerId) => makeReminder(ownerId),
act: async (row) => {
const { POST } =
await import("@/app/api/measurement-reminders/[id]/complete/route");
return call(
POST as Handler,
"POST",
`/api/measurement-reminders/${row.id}/complete`,
{},
{ id: row.id },
);
},
ok: 200,
auditAction: "measurementReminder.complete",
applied: async (row) =>
(
await getPrismaClient().measurementReminder.findUnique({
where: { id: row.id },
})
)?.lastSatisfiedAt !== null,
});
manageContract("POST /api/measurement-reminders/[id]/satisfy", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/measurement-reminders/[id]/satisfy",
)!,
prepare: (ownerId) => makeReminder(ownerId),
act: async (row) => {
const { POST } =
await import("@/app/api/measurement-reminders/[id]/satisfy/route");
return call(
POST as Handler,
"POST",
`/api/measurement-reminders/${row.id}/satisfy`,
{},
{ id: row.id },
);
},
ok: 200,
auditAction: "measurementReminder.satisfy",
applied: async (row) =>
(
await getPrismaClient().measurementReminder.findUnique({
where: { id: row.id },
})
)?.lastSatisfiedAt !== null,
});
/* -------------------------------------------------------------------------- */
/* 1b — labs, catalog entries, allergies, family, and illness */
/* -------------------------------------------------------------------------- */
writeEffectContract("POST /api/labs", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/labs" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/labs/route");
return call(POST as Handler, "POST", "/api/labs", {
analyte: "HbA1c",
value: 5.2,
unit: "%",
takenAt: ISO,
});
},
ok: 201,
auditAction: "labResult.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().labResult.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("DELETE /api/labs/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/labs/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makeLabResult(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/labs/[id]/route");
return call(DELETE as Handler, "DELETE", `/api/labs/${row.id}`, undefined, {
id: row.id,
});
},
ok: 200,
auditAction: "labResult.delete",
applied: async (row) =>
(await getPrismaClient().labResult.findUnique({ where: { id: row.id } }))
?.deletedAt !== null,
});
manageContract("POST /api/labs/restore", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/labs/restore",
)!,
prepare: async (ownerId) => {
const row = await makeLabResult(ownerId);
return getPrismaClient().labResult.update({
where: { id: row.id },
data: { deletedAt: new Date() },
});
},
act: async (row) => {
const { POST } = await import("@/app/api/labs/restore/route");
return call(POST as Handler, "POST", "/api/labs/restore", {
ids: [row.id],
});
},
ok: 200,
auditAction: "labResult.restore",
applied: async (row) =>
(await getPrismaClient().labResult.findUnique({ where: { id: row.id } }))
?.deletedAt === null,
});
writeEffectContract("POST /api/biomarkers", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/biomarkers",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/biomarkers/route");
return call(POST as Handler, "POST", "/api/biomarkers", {
name: "Ferritin",
unit: "µg/L",
});
},
ok: 201,
auditAction: "biomarker.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().biomarker.count({
where: { userId: ownerId, name: "Ferritin" },
})) === 1,
});
manageContract("PUT /api/biomarkers/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/biomarkers/[id]" && entry.action === "PUT",
)!,
prepare: async (userId) =>
getPrismaClient().biomarker.create({
data: { userId, name: "Ferritin", unit: "µg/L" },
}),
act: async (row) => {
const { PUT } = await import("@/app/api/biomarkers/[id]/route");
return call(
PUT as Handler,
"PUT",
`/api/biomarkers/${row.id}`,
{ unit: "ng/mL" },
{ id: row.id },
);
},
ok: 200,
auditAction: "biomarker.update",
applied: async (row) =>
(await getPrismaClient().biomarker.findUnique({ where: { id: row.id } }))
?.unit === "ng/mL",
});
manageContract("DELETE /api/biomarkers/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/biomarkers/[id]" && entry.action === "DELETE",
)!,
prepare: async (userId) =>
getPrismaClient().biomarker.create({
data: { userId, name: "Transferrin", unit: "g/L" },
}),
act: async (row) => {
const { DELETE } = await import("@/app/api/biomarkers/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/biomarkers/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "biomarker.delete",
applied: async (row) =>
(await getPrismaClient().biomarker.findUnique({
where: { id: row.id },
})) === null,
});
manageContract("POST /api/allergies", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/allergies" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/allergies/route");
return call(POST as Handler, "POST", "/api/allergies", {
substance: "Pollen",
category: "ENVIRONMENT",
});
},
ok: 201,
auditAction: "allergy.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().allergy.count({
where: { userId: ownerId, substance: "Pollen" },
})) === 1,
});
manageContract("DELETE /api/allergies/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/allergies/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makeAllergy(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/allergies/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/allergies/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "allergy.delete",
applied: async (row) =>
(await getPrismaClient().allergy.findUnique({ where: { id: row.id } }))
?.deletedAt !== null,
});
manageContract("POST /api/family-history", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/family-history" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/family-history/route");
return call(POST as Handler, "POST", "/api/family-history", {
relationship: "MOTHER",
condition: "Hypertension",
});
},
ok: 201,
auditAction: "family-history.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().familyHistoryEntry.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("PATCH /api/family-history/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/family-history/[id]" && entry.action === "PATCH",
)!,
prepare: (ownerId) => makeFamilyHistory(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/family-history/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/family-history/${row.id}`,
{ ageAtOnset: 50 },
{ id: row.id },
);
},
ok: 200,
auditAction: "family-history.update",
applied: async (row) =>
(
await getPrismaClient().familyHistoryEntry.findUnique({
where: { id: row.id },
})
)?.ageAtOnset === 50,
});
manageContract("DELETE /api/family-history/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/family-history/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makeFamilyHistory(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/family-history/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/family-history/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "family-history.delete",
applied: async (row) =>
(
await getPrismaClient().familyHistoryEntry.findUnique({
where: { id: row.id },
})
)?.deletedAt !== null,
});
writeEffectContract("POST /api/illness/episodes", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/illness/episodes" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/illness/episodes/route");
return call(POST as Handler, "POST", "/api/illness/episodes", {
label: "Influenza",
type: "INFECTION",
});
},
ok: 201,
auditAction: "illness.episode.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().illnessEpisode.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("PATCH /api/illness/episodes/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/illness/episodes/[id]" && entry.action === "PATCH",
)!,
prepare: (ownerId) => makeEpisode(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/illness/episodes/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/illness/episodes/${row.id}`,
{ label: "Influenza A" },
{ id: row.id },
);
},
ok: 200,
auditAction: "illness.episode.update",
applied: async (row) =>
(
await getPrismaClient().illnessEpisode.findUnique({
where: { id: row.id },
})
)?.label === "Influenza A",
});
manageContract("DELETE /api/illness/episodes/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/illness/episodes/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makeEpisode(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/illness/episodes/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/illness/episodes/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "illness.episode.delete",
applied: async (row) =>
(
await getPrismaClient().illnessEpisode.findUnique({
where: { id: row.id },
})
)?.deletedAt !== null,
});
manageContract("PATCH /api/illness/episodes/[id]/resolve", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/illness/episodes/[id]/resolve",
)!,
prepare: (ownerId) => makeEpisode(ownerId),
act: async (row) => {
const { PATCH } =
await import("@/app/api/illness/episodes/[id]/resolve/route");
return call(
PATCH as Handler,
"PATCH",
`/api/illness/episodes/${row.id}/resolve`,
{},
{ id: row.id },
);
},
ok: 200,
auditAction: "illness.episode.resolve",
applied: async (row) =>
(
await getPrismaClient().illnessEpisode.findUnique({
where: { id: row.id },
})
)?.resolvedAt !== null,
});
manageContract("POST /api/illness/episodes/[id]/restore", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/illness/episodes/[id]/restore",
)!,
prepare: async (ownerId) => {
const row = await makeEpisode(ownerId);
return getPrismaClient().illnessEpisode.update({
where: { id: row.id },
data: { deletedAt: new Date() },
});
},
act: async (row) => {
const { POST } =
await import("@/app/api/illness/episodes/[id]/restore/route");
return call(
POST as Handler,
"POST",
`/api/illness/episodes/${row.id}/restore`,
{},
{ id: row.id },
);
},
ok: 200,
auditAction: "illness.episode.restore",
applied: async (row) =>
(
await getPrismaClient().illnessEpisode.findUnique({
where: { id: row.id },
})
)?.deletedAt === null,
});
async function makeSymptomDefinition(userId: string) {
return getPrismaClient().symptomDefinition.create({
data: {
userId,
labelEncrypted: encryptToBytes("Aura"),
icon: "Zap",
},
});
}
async function makeSymptomEvent(userId: string) {
const definition = await makeSymptomDefinition(userId);
return getPrismaClient().symptomEvent.create({
data: {
userId,
definitionId: definition.id,
occurredAt: new Date(ISO),
intensity: 6,
},
});
}
manageContract("POST /api/symptoms/definitions", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/symptoms/definitions" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/symptoms/definitions/route");
return call(POST as Handler, "POST", "/api/symptoms/definitions", {
label: "Aura",
icon: "Zap",
});
},
ok: 201,
auditAction: "symptoms.definition.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().symptomDefinition.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("PATCH /api/symptoms/definitions/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/symptoms/definitions/[id]" &&
entry.action === "PATCH",
)!,
prepare: (ownerId) => makeSymptomDefinition(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/symptoms/definitions/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/symptoms/definitions/${row.id}`,
{ icon: "Brain" },
{ id: row.id },
);
},
ok: 200,
auditAction: "symptoms.definition.update",
applied: async (row) =>
(
await getPrismaClient().symptomDefinition.findUnique({
where: { id: row.id },
})
)?.icon === "Brain",
});
manageContract("DELETE /api/symptoms/definitions/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/symptoms/definitions/[id]" &&
entry.action === "DELETE",
)!,
prepare: (ownerId) => makeSymptomDefinition(ownerId),
act: async (row) => {
const { DELETE } =
await import("@/app/api/symptoms/definitions/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/symptoms/definitions/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "symptoms.definition.delete",
applied: async (row) =>
(
await getPrismaClient().symptomDefinition.findUnique({
where: { id: row.id },
})
)?.isActive === false,
});
writeEffectContract("POST /api/symptoms/events", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/symptoms/events" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({
ownerId,
definition: await makeSymptomDefinition(ownerId),
}),
act: async ({ definition }) => {
const { POST } = await import("@/app/api/symptoms/events/route");
return call(POST as Handler, "POST", "/api/symptoms/events", {
definitionId: definition.id,
intensity: 7,
});
},
ok: 201,
auditAction: "symptoms.event.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().symptomEvent.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("PATCH /api/symptoms/events/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/symptoms/events/[id]" && entry.action === "PATCH",
)!,
prepare: (ownerId) => makeSymptomEvent(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/symptoms/events/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/symptoms/events/${row.id}`,
{ intensity: 9 },
{ id: row.id },
);
},
ok: 200,
auditAction: "symptoms.event.update",
applied: async (row) =>
(
await getPrismaClient().symptomEvent.findUnique({
where: { id: row.id },
})
)?.intensity === 9,
});
manageContract("DELETE /api/symptoms/events/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/symptoms/events/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makeSymptomEvent(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/symptoms/events/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/symptoms/events/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "symptoms.event.delete",
applied: async (row) =>
(await getPrismaClient().symptomEvent.findUnique({
where: { id: row.id },
})) === null,
});
writeEffectContract("POST /api/encounters", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/encounters" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/encounters/route");
return call(POST as Handler, "POST", "/api/encounters", {
occurredAt: ISO,
status: "DONE",
});
},
ok: 201,
auditAction: "encounter.visit.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().encounter.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("PATCH /api/encounters/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/encounters/[id]" && entry.action === "PATCH",
)!,
prepare: (ownerId) => makeVisit(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/encounters/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/encounters/${row.id}`,
{ kind: "SPECIALIST" },
{ id: row.id },
);
},
ok: 200,
auditAction: "encounter.visit.update",
applied: async (row) =>
(await getPrismaClient().encounter.findUnique({ where: { id: row.id } }))
?.kind === "SPECIALIST",
});
manageContract("DELETE /api/encounters/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/encounters/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makeVisit(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/encounters/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/encounters/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "encounter.visit.delete",
applied: async (row) =>
(await getPrismaClient().encounter.findUnique({ where: { id: row.id } }))
?.deletedAt !== null,
});
manageContract("POST /api/encounters/[id]/restore", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/encounters/[id]/restore",
)!,
prepare: async (ownerId) => {
const row = await makeVisit(ownerId);
return getPrismaClient().encounter.update({
where: { id: row.id },
data: { deletedAt: new Date() },
});
},
act: async (row) => {
const { POST } = await import("@/app/api/encounters/[id]/restore/route");
return call(
POST as Handler,
"POST",
`/api/encounters/${row.id}/restore`,
{},
{ id: row.id },
);
},
ok: 200,
auditAction: "encounter.visit.restore",
applied: async (row) =>
(await getPrismaClient().encounter.findUnique({ where: { id: row.id } }))
?.deletedAt === null,
});
writeEffectContract("POST /api/practitioners", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/practitioners" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/practitioners/route");
return call(POST as Handler, "POST", "/api/practitioners", {
name: "Praxis Nord",
});
},
ok: 201,
auditAction: "practitioner.contact.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().practitioner.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("PATCH /api/practitioners/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/practitioners/[id]" && entry.action === "PATCH",
)!,
prepare: (ownerId) => makePractitioner(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/practitioners/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/practitioners/${row.id}`,
{ name: "Praxis Süd" },
{ id: row.id },
);
},
ok: 200,
auditAction: "practitioner.contact.update",
applied: async (row) =>
(await getPrismaClient().practitioner.findUnique({ where: { id: row.id } }))
?.name === "Praxis Süd",
});
manageContract("DELETE /api/practitioners/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/practitioners/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makePractitioner(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/practitioners/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/practitioners/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "practitioner.contact.delete",
applied: async (row) =>
(await getPrismaClient().practitioner.findUnique({ where: { id: row.id } }))
?.deletedAt !== null,
});
manageContract("POST /api/practitioners/[id]/restore", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/practitioners/[id]/restore",
)!,
prepare: async (ownerId) => {
const row = await makePractitioner(ownerId);
return getPrismaClient().practitioner.update({
where: { id: row.id },
data: { deletedAt: new Date() },
});
},
act: async (row) => {
const { POST } = await import("@/app/api/practitioners/[id]/restore/route");
return call(
POST as Handler,
"POST",
`/api/practitioners/${row.id}/restore`,
{},
{ id: row.id },
);
},
ok: 200,
auditAction: "practitioner.contact.restore",
applied: async (row) =>
(await getPrismaClient().practitioner.findUnique({ where: { id: row.id } }))
?.deletedAt === null,
});
writeEffectContract("POST /api/vaccinations", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/vaccinations" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/vaccinations/route");
return call(POST as Handler, "POST", "/api/vaccinations", {
occurredAt: ISO,
antigenSlug: "tetanus",
});
},
ok: 201,
auditAction: "vaccination.record.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().vaccinationRecord.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("PATCH /api/vaccinations/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/vaccinations/[id]" && entry.action === "PATCH",
)!,
prepare: (ownerId) => makeVaccination(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/vaccinations/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/vaccinations/${row.id}`,
{ lotNumber: "MX-2211" },
{ id: row.id },
);
},
ok: 200,
auditAction: "vaccination.record.update",
applied: async (row) =>
(
await getPrismaClient().vaccinationRecord.findUnique({
where: { id: row.id },
})
)?.lotNumber === "MX-2211",
});
manageContract("DELETE /api/vaccinations/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/vaccinations/[id]" && entry.action === "DELETE",
)!,
prepare: (ownerId) => makeVaccination(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/vaccinations/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/vaccinations/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "vaccination.record.delete",
applied: async (row) =>
(
await getPrismaClient().vaccinationRecord.findUnique({
where: { id: row.id },
})
)?.deletedAt !== null,
});
manageContract("POST /api/vaccinations/[id]/restore", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) => entry.route === "/api/vaccinations/[id]/restore",
)!,
prepare: async (ownerId) => {
const row = await makeVaccination(ownerId);
return getPrismaClient().vaccinationRecord.update({
where: { id: row.id },
data: { deletedAt: new Date() },
});
},
act: async (row) => {
const { POST } = await import("@/app/api/vaccinations/[id]/restore/route");
return call(
POST as Handler,
"POST",
`/api/vaccinations/${row.id}/restore`,
{},
{ id: row.id },
);
},
ok: 200,
auditAction: "vaccination.record.restore",
applied: async (row) =>
(
await getPrismaClient().vaccinationRecord.findUnique({
where: { id: row.id },
})
)?.deletedAt === null,
});
writeEffectContract("POST /api/vaccinations/custom", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/vaccinations/custom" && entry.action === "POST",
)!,
prepare: async (ownerId) => ({ ownerId }),
act: async () => {
const { POST } = await import("@/app/api/vaccinations/custom/route");
return call(POST as Handler, "POST", "/api/vaccinations/custom", {
name: "Cholera oral",
components: ["cholera"],
});
},
ok: 201,
auditAction: "vaccination.custom.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().customVaccine.count({
where: { userId: ownerId },
})) === 1,
});
manageContract("PATCH /api/vaccinations/custom/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/vaccinations/custom/[id]" &&
entry.action === "PATCH",
)!,
prepare: (ownerId) => makeCustomVaccine(ownerId),
act: async (row) => {
const { PATCH } = await import("@/app/api/vaccinations/custom/[id]/route");
return call(
PATCH as Handler,
"PATCH",
`/api/vaccinations/custom/${row.id}`,
{ boosterIntervalMonths: 36 },
{ id: row.id },
);
},
ok: 200,
auditAction: "vaccination.custom.update",
applied: async (row) =>
(
await getPrismaClient().customVaccine.findUnique({
where: { id: row.id },
})
)?.boosterIntervalMonths === 36,
});
manageContract("DELETE /api/vaccinations/custom/[id]", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/vaccinations/custom/[id]" &&
entry.action === "DELETE",
)!,
prepare: (ownerId) => makeCustomVaccine(ownerId),
act: async (row) => {
const { DELETE } = await import("@/app/api/vaccinations/custom/[id]/route");
return call(
DELETE as Handler,
"DELETE",
`/api/vaccinations/custom/${row.id}`,
undefined,
{ id: row.id },
);
},
ok: 200,
auditAction: "vaccination.custom.delete",
applied: async (row) =>
(
await getPrismaClient().customVaccine.findUnique({
where: { id: row.id },
})
)?.deletedAt !== null,
});
proveWriteRoute("POST /api/vaccinations/[id]/booster", {
route: "/api/vaccinations/[id]/booster",
// The dose the booster is planned against. `makeVaccination` records a
// monovalent tetanus dose, so the mint keys the reminder on the tetanus
// antigen and `applied` can prove exactly one reminder now carries it.
prepare: async (ownerId) => ({
ownerId,
vaccination: await makeVaccination(ownerId),
}),
params: ({ vaccination }) => ({ id: vaccination.id }),
body: () => ({ intervalMonths: 120, label: "Tetanus booster" }),
ok: 201,
auditAction: "vaccination.booster.planned",
applied: async ({ ownerId }) =>
(await getPrismaClient().measurementReminder.count({
where: { userId: ownerId, vaccinationAntigen: "tetanus" },
})) === 1,
});
manageContract("POST /api/illness/episodes/[id]/day-logs", {
contract: ADMITTED_MUTATING_HANDLERS.find(
(entry) =>
entry.route === "/api/illness/episodes/[id]/day-logs" &&
entry.action === "POST",
)!,
prepare: (ownerId) => makeEpisode(ownerId),
act: async (row) => {
const { POST } =
await import("@/app/api/illness/episodes/[id]/day-logs/route");
return call(
POST as Handler,
"POST",
`/api/illness/episodes/${row.id}/day-logs`,
{ date: "2026-07-15", feverC: 38.1 },
{ id: row.id },
);
},
ok: 201,
auditAction: "illness.day-log.upsert",
applied: async (row) =>
(await getPrismaClient().illnessDayLog.count({
where: { episodeId: row.id, feverC: 38.1 },
})) === 1,
});
/* -------------------------------------------------------------------------- */
/* 1c — the medication record, its events, supply, and import queue */
/* -------------------------------------------------------------------------- */
proveWriteRoute("POST /api/medications/[id]/side-effects", {
route: "/api/medications/[id]/side-effects",
prepare: async (ownerId) => ({
ownerId,
medication: await makeMedication(ownerId),
}),
params: ({ medication }) => ({ id: medication.id }),
body: () => ({ entry: "NAUSEA", severity: 3 }),
ok: 201,
auditAction: "medication.sideEffect.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().medicationSideEffect.count({
where: { userId: ownerId },
})) === 1,
});
proveWriteRoute("POST /api/medications", {
route: "/api/medications",
prepare: async (ownerId) => ({ ownerId }),
body: () => ({
name: "Metformin",
dose: "500mg",
schedules: [{ windowStart: "08:00", windowEnd: "10:00" }],
}),
ok: 201,
auditAction: "medication.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().medication.count({
where: { userId: ownerId, name: "Metformin" },
})) === 1,
});
proveWriteRoute("POST /api/medications/intake", {
route: "/api/medications/intake",
prepare: async (ownerId) => ({
ownerId,
...(await makeMedicationIntake(ownerId)),
}),
body: ({ event }) => ({ intakeId: event.id, status: "taken" }),
ok: 200,
auditAction: "medications.intake.update",
applied: async ({ ownerId }) =>
(await getPrismaClient().medicationIntakeEvent.count({
where: { userId: ownerId, takenAt: { not: null }, deletedAt: null },
})) === 1,
});
proveWriteRoute("POST /api/medications/[id]/intake", {
route: "/api/medications/[id]/intake",
prepare: async (ownerId) => ({
ownerId,
medication: await makeMedication(ownerId),
}),
params: ({ medication }) => ({ id: medication.id }),
body: () => ({ takenAt: new Date().toISOString() }),
ok: 201,
auditAction: "medication.intake",
applied: async ({ ownerId }) =>
(await getPrismaClient().medicationIntakeEvent.count({
where: { userId: ownerId, takenAt: { not: null } },
})) === 1,
});
proveManageRoute("PUT /api/medications/[id]", {
route: "/api/medications/[id]",
action: "PUT",
prepare: (ownerId) => makeMedication(ownerId),
params: (medication) => ({ id: medication.id }),
body: () => ({ dose: "20mg" }),
ok: 200,
auditAction: "medication.update",
applied: async (medication) =>
(
await getPrismaClient().medication.findUnique({
where: { id: medication.id },
})
)?.dose === "20mg",
});
proveManageRoute("POST /api/medications/[id]/schedule-revisions", {
route: "/api/medications/[id]/schedule-revisions",
prepare: async (ownerId) => ({
ownerId,
medication: await makeMedication(ownerId),
}),
params: ({ medication }) => ({ id: medication.id }),
body: () => ({
validFrom: new Date(Date.now() - 3 * 86_400_000).toISOString(),
validUntil: new Date(Date.now() - 2 * 86_400_000).toISOString(),
timesOfDay: ["08:00"],
}),
ok: 201,
auditAction: "medication.schedule_revision.created",
applied: async ({ medication }) =>
(await getPrismaClient().medicationScheduleRevision.count({
where: { medicationId: medication.id },
})) === 1,
});
proveManageRoute(
"PATCH /api/medications/[id]/schedule-revisions/[revisionId]",
{
route: "/api/medications/[id]/schedule-revisions/[revisionId]",
action: "PATCH",
prepare: async (ownerId) => ({
ownerId,
...(await makeScheduleRevision(ownerId)),
targetValidFrom: new Date(Date.now() - 4 * 86_400_000),
targetValidUntil: new Date(Date.now() - 3 * 86_400_000),
}),
params: ({ medication, revision }) => ({
id: medication.id,
revisionId: revision.id,
}),
body: ({ targetValidFrom, targetValidUntil }) => ({
validFrom: targetValidFrom.toISOString(),
validUntil: targetValidUntil.toISOString(),
timesOfDay: ["09:00"],
}),
ok: 200,
auditAction: "medication.schedule_revision.updated",
applied: async ({ revision, targetValidFrom }) =>
(
await getPrismaClient().medicationScheduleRevision.findUnique({
where: { id: revision.id },
})
)?.validFrom.toISOString() === targetValidFrom.toISOString(),
},
);
proveManageRoute(
"DELETE /api/medications/[id]/schedule-revisions/[revisionId]",
{
route: "/api/medications/[id]/schedule-revisions/[revisionId]",
action: "DELETE",
prepare: async (ownerId) => ({
ownerId,
...(await makeScheduleRevision(ownerId)),
}),
params: ({ medication, revision }) => ({
id: medication.id,
revisionId: revision.id,
}),
ok: 200,
auditAction: "medication.schedule_revision.deleted",
applied: async ({ revision }) =>
(await getPrismaClient().medicationScheduleRevision.findUnique({
where: { id: revision.id },
})) === null,
},
);
proveManageRoute("POST /api/medications/[id]/inventory", {
route: "/api/medications/[id]/inventory",
prepare: async (ownerId) => ({
ownerId,
medication: await makeMedication(ownerId),
}),
params: ({ medication }) => ({ id: medication.id }),
body: () => ({ unitsTotal: 20, containerType: "BOTTLE" }),
ok: 201,
auditAction: "medication.inventory.create",
applied: async ({ medication }) =>
(await getPrismaClient().medicationInventoryItem.count({
where: { medicationId: medication.id },
})) === 1,
});
proveManageRoute("PATCH /api/medications/[id]/inventory/[itemId]", {
route: "/api/medications/[id]/inventory/[itemId]",
action: "PATCH",
prepare: async (ownerId) => ({
ownerId,
...(await makeInventoryItem(ownerId)),
}),
params: ({ medication, item }) => ({ id: medication.id, itemId: item.id }),
body: () => ({ unitsRemaining: 15 }),
ok: 200,
auditAction: "medication.inventory.update",
applied: async ({ item }) =>
Number(
(
await getPrismaClient().medicationInventoryItem.findUnique({
where: { id: item.id },
})
)?.unitsRemaining,
) === 15,
});
proveManageRoute("DELETE /api/medications/[id]/inventory/[itemId]", {
route: "/api/medications/[id]/inventory/[itemId]",
action: "DELETE",
prepare: async (ownerId) => ({
ownerId,
...(await makeInventoryItem(ownerId)),
}),
params: ({ medication, item }) => ({ id: medication.id, itemId: item.id }),
ok: 200,
auditAction: "medication.inventory.delete",
applied: async ({ item }) =>
(await getPrismaClient().medicationInventoryItem.findUnique({
where: { id: item.id },
})) === null,
});
proveManageRoute("DELETE /api/medications/[id]/side-effects/[logId]", {
route: "/api/medications/[id]/side-effects/[logId]",
action: "DELETE",
prepare: async (ownerId) => {
const medication = await makeMedication(ownerId);
const log = await getPrismaClient().medicationSideEffect.create({
data: {
userId: ownerId,
medicationId: medication.id,
category: "GI",
entry: "NAUSEA",
severity: 3,
},
});
return { ownerId, medication, log };
},
params: ({ medication, log }) => ({ id: medication.id, logId: log.id }),
ok: 200,
auditAction: "medication.sideEffect.delete",
applied: async ({ log }) =>
(await getPrismaClient().medicationSideEffect.findUnique({
where: { id: log.id },
})) === null,
});
proveManageRoute("POST /api/medications/[id]/glp1", {
route: "/api/medications/[id]/glp1",
prepare: async (ownerId) => ({
ownerId,
medication: await getPrismaClient().medication.create({
data: {
userId: ownerId,
name: "Semaglutide",
dose: "0.25mg",
treatmentClass: "GLP1",
},
}),
}),
params: ({ medication }) => ({ id: medication.id }),
body: () => ({
doseChange: { effectiveFrom: ISO, doseValue: 0.5, doseUnit: "mg" },
}),
ok: 201,
auditAction: "medication.glp1.update",
applied: async ({ medication }) =>
(await getPrismaClient().medicationDoseChange.count({
where: { medicationId: medication.id },
})) === 1,
});
proveManageRoute("PUT /api/medications/[id]/ingredients", {
route: "/api/medications/[id]/ingredients",
action: "PUT",
prepare: async (ownerId) => {
// The composition lives behind the opt-in nutrients module.
await getPrismaClient().user.update({
where: { id: ownerId },
data: { modulePreferencesJson: { nutrients: true } },
});
return {
ownerId,
medication: await getPrismaClient().medication.create({
data: { userId: ownerId, name: "Vitamin D3", dose: "1000 IU" },
}),
};
},
params: ({ medication }) => ({ id: medication.id }),
body: () => ({
ingredients: [{ nutrient: "vitamin_d", amount: 1000, unit: "IU" }],
}),
ok: 200,
auditAction: "medication.ingredients.update",
applied: async ({ medication }) =>
(
await getPrismaClient().medicationIngredient.findFirst({
where: { medicationId: medication.id },
})
)?.amountPerUnit === 25,
});
proveManageRoute("PUT /api/medications/[id]/intake/[eventId]", {
route: "/api/medications/[id]/intake/[eventId]",
action: "PUT",
prepare: async (ownerId) => ({
ownerId,
...(await makeMedicationIntake(ownerId)),
}),
params: ({ medication, event }) => ({ id: medication.id, eventId: event.id }),
body: () => ({ skipped: true }),
ok: 200,
auditAction: "medication.intake.update",
applied: async ({ event }) =>
(
await getPrismaClient().medicationIntakeEvent.findUnique({
where: { id: event.id },
})
)?.skipped === true,
});
proveManageRoute("DELETE /api/medications/[id]/intake/[eventId]", {
route: "/api/medications/[id]/intake/[eventId]",
action: "DELETE",
prepare: async (ownerId) => ({
ownerId,
...(await makeMedicationIntake(ownerId)),
}),
params: ({ medication, event }) => ({ id: medication.id, eventId: event.id }),
ok: 200,
auditAction: "medication.intake.delete",
applied: async ({ event }) =>
(
await getPrismaClient().medicationIntakeEvent.findUnique({
where: { id: event.id },
})
)?.deletedAt !== null,
});
proveManageRoute("POST /api/medications/[id]/intake/bulk-delete", {
route: "/api/medications/[id]/intake/bulk-delete",
prepare: async (ownerId) => ({
ownerId,
...(await makeMedicationIntake(ownerId)),
}),
params: ({ medication }) => ({ id: medication.id }),
body: ({ event }) => ({ eventIds: [event.id] }),
ok: 200,
auditAction: "medication.intake.bulk_delete",
applied: async ({ event }) =>
(
await getPrismaClient().medicationIntakeEvent.findUnique({
where: { id: event.id },
})
)?.deletedAt !== null,
});
proveManageRoute("DELETE /api/medications/[id]/intake/purge", {
route: "/api/medications/[id]/intake/purge",
action: "DELETE",
prepare: async (ownerId) => ({
ownerId,
...(await makeMedicationIntake(ownerId)),
}),
params: ({ medication }) => ({ id: medication.id }),
ok: 200,
auditAction: "medication.intake.purge",
applied: async ({ event }) =>
(
await getPrismaClient().medicationIntakeEvent.findUnique({
where: { id: event.id },
})
)?.deletedAt !== null,
});
proveManageRoute("POST /api/medications/[id]/intake/import", {
route: "/api/medications/[id]/intake/import",
prepare: async (ownerId) => ({
ownerId,
medication: await makeMedication(ownerId),
}),
params: ({ medication }) => ({ id: medication.id }),
body: () => [{ datum: "2026-07-15", uhrzeit: "08:00:00" }],
ok: 202,
auditAction: "medication.intake.import.kickoff",
applied: async ({ ownerId }) =>
(await getPrismaClient().medicationIntakeImportJob.count({
where: { recordUserId: ownerId },
})) === 1,
});
manageContract("POST /api/medications/intake/dose-history-import", {
contract: admittedContract("/api/medications/intake/dose-history-import"),
prepare: async (ownerId) => ({
ownerId,
medication: await makeMedication(ownerId, "Metformin"),
}),
act: async () => {
const handler = await matrixHandler(
admittedContract("/api/medications/intake/dose-history-import"),
);
const csv = [
"Date,Scheduled Date,Medication,Nickname,Dosage,Scheduled Dosage,Unit,Status,Archived,Codings",
"2026-07-15 08:00:00 +0000,2026-07-15 08:00:00 +0000,Metformin,Metformin,500,500,mg,Taken,No,",
].join("\n");
return handler(
new NextRequest(
"http://localhost/api/medications/intake/dose-history-import",
{
method: "POST",
headers: { "content-type": "text/csv" },
body: csv,
},
),
{ params: Promise.resolve({}) },
);
},
ok: 202,
auditAction: "medication.intake.history.import.kickoff",
applied: async ({ ownerId }) =>
(await getPrismaClient().medicationIntakeImportJob.count({
where: { recordUserId: ownerId },
})) === 1,
});
/* -------------------------------------------------------------------------- */
/* 1d — mood entries and validated mental-health assessments */
/* -------------------------------------------------------------------------- */
async function makeMoodEntry(userId: string) {
return getPrismaClient().moodEntry.create({
data: {
userId,
date: "2026-07-15",
mood: "GUT",
score: 4,
moodLoggedAt: new Date(ISO),
source: "MANUAL",
},
});
}
proveManageRoute("POST /api/mood-entries", {
route: "/api/mood-entries",
prepare: async (ownerId) => ({ ownerId }),
body: () => ({ mood: "GUT", moodLoggedAt: ISO }),
ok: 201,
auditAction: "moodEntry.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().moodEntry.count({
where: { userId: ownerId },
})) === 1,
});
proveManageRoute("PUT /api/mood-entries/[id]", {
route: "/api/mood-entries/[id]",
action: "PUT",
prepare: (ownerId) => makeMoodEntry(ownerId),
params: (entry) => ({ id: entry.id }),
body: () => ({ mood: "OKAY" }),
ok: 200,
auditAction: "moodEntry.update",
applied: async (entry) =>
(await getPrismaClient().moodEntry.findUnique({ where: { id: entry.id } }))
?.mood === "OKAY",
});
proveManageRoute("DELETE /api/mood-entries/[id]", {
route: "/api/mood-entries/[id]",
action: "DELETE",
prepare: (ownerId) => makeMoodEntry(ownerId),
params: (entry) => ({ id: entry.id }),
ok: 200,
auditAction: "moodEntry.delete",
applied: async (entry) =>
(await getPrismaClient().moodEntry.findUnique({ where: { id: entry.id } }))
?.deletedAt !== null,
});
proveManageRoute("POST /api/mood-entries/bulk-delete", {
route: "/api/mood-entries/bulk-delete",
prepare: (ownerId) => makeMoodEntry(ownerId),
body: (entry) => ({ ids: [entry.id] }),
ok: 200,
auditAction: "mood.delete.bulk",
applied: async (entry) =>
(await getPrismaClient().moodEntry.findUnique({ where: { id: entry.id } }))
?.deletedAt !== null,
});
proveManageRoute("POST /api/mood-entries/restore", {
route: "/api/mood-entries/restore",
prepare: async (ownerId) => {
const entry = await makeMoodEntry(ownerId);
return getPrismaClient().moodEntry.update({
where: { id: entry.id },
data: { deletedAt: new Date() },
});
},
body: (entry) => ({ ids: [entry.id] }),
ok: 200,
auditAction: "mood.restore",
applied: async (entry) =>
(await getPrismaClient().moodEntry.findUnique({ where: { id: entry.id } }))
?.deletedAt === null,
});
proveManageRoute("POST /api/mental-health/assessments", {
route: "/api/mental-health/assessments",
prepare: async (ownerId) => ({ ownerId }),
body: () => ({
instrument: "PHQ9",
items: [0, 0, 0, 0, 0, 0, 0, 0, 0],
takenAt: ISO,
}),
ok: 201,
auditAction: "mental-health.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().mentalHealthAssessment.count({
where: { userId: ownerId },
})) === 1,
});
/* -------------------------------------------------------------------------- */
/* 1e — cycle and record-wide mutations */
/* -------------------------------------------------------------------------- */
async function enableCycle(userId: string) {
await getPrismaClient().user.update({
where: { id: userId },
data: { gender: "FEMALE" },
});
return userId;
}
async function makeCycleDayLog(userId: string) {
await enableCycle(userId);
return getPrismaClient().cycleDayLog.create({
data: {
userId,
date: "2026-07-15",
source: "MANUAL",
},
});
}
proveManageRoute("DELETE /api/cycle/cycles/[id]", {
route: "/api/cycle/cycles/[id]",
action: "DELETE",
prepare: async (ownerId) => {
await enableCycle(ownerId);
return getPrismaClient().menstrualCycle.create({
data: { userId: ownerId, startDate: "2026-07-01" },
});
},
params: (cycle) => ({ id: cycle.id }),
ok: 204,
auditAction: "cycle.cycle.delete",
applied: async (cycle) =>
(
await getPrismaClient().menstrualCycle.findUnique({
where: { id: cycle.id },
})
)?.deletedAt !== null,
});
proveManageRoute("POST /api/cycle/day-logs", {
route: "/api/cycle/day-logs",
prepare: async (ownerId) => ({ ownerId: await enableCycle(ownerId) }),
body: () => ({ date: "2026-07-15", loggedAt: ISO, flow: "LIGHT" }),
ok: 201,
auditAction: "cycle.day-log.upsert",
applied: async ({ ownerId }) =>
(await getPrismaClient().cycleDayLog.count({
where: { userId: ownerId, date: "2026-07-15" },
})) === 1,
});
proveManageRoute("PATCH /api/cycle/day-logs/[id]", {
route: "/api/cycle/day-logs/[id]",
action: "PATCH",
prepare: (ownerId) => makeCycleDayLog(ownerId),
params: (row) => ({ id: row.id }),
body: () => ({ flow: "HEAVY" }),
ok: 200,
auditAction: "cycle.day-log.update",
applied: async (row) =>
(await getPrismaClient().cycleDayLog.findUnique({ where: { id: row.id } }))
?.flow === "HEAVY",
});
proveManageRoute("DELETE /api/cycle/day-logs/[id]", {
route: "/api/cycle/day-logs/[id]",
action: "DELETE",
prepare: (ownerId) => makeCycleDayLog(ownerId),
params: (row) => ({ id: row.id }),
ok: 204,
auditAction: "cycle.day-log.delete",
applied: async (row) =>
(await getPrismaClient().cycleDayLog.findUnique({ where: { id: row.id } }))
?.deletedAt !== null,
});
proveManageRoute("POST /api/cycle/period", {
route: "/api/cycle/period",
prepare: async (ownerId) => ({ ownerId: await enableCycle(ownerId) }),
body: () => ({ action: "start", date: "2026-07-15", loggedAt: ISO }),
ok: 200,
auditAction: "cycle.period.boundary",
applied: async ({ ownerId }) =>
(await getPrismaClient().menstrualCycle.count({
where: { userId: ownerId, startDate: "2026-07-15" },
})) === 1,
});
proveManageRoute("POST /api/cycle/symptoms/custom", {
route: "/api/cycle/symptoms/custom",
prepare: async (ownerId) => ({ ownerId: await enableCycle(ownerId) }),
body: () => ({ label: "Pelvic pressure" }),
ok: 201,
auditAction: "cycle.symptom.custom.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().cycleSymptom.count({
where: { userId: ownerId },
})) === 1,
});
proveManageRoute("POST /api/medications/categories", {
route: "/api/medications/categories",
prepare: async (ownerId) => ({ ownerId }),
body: () => ({ label: "Travel kit" }),
ok: 201,
auditAction: "medication.category.custom.create",
applied: async ({ ownerId }) =>
(await getPrismaClient().medicationCategoryLabel.count({
where: { userId: ownerId },
})) === 1,
});
proveManageRoute("POST /api/nutrients/water", {
route: "/api/nutrients/water",
prepare: async (ownerId) => {
await getPrismaClient().user.update({
where: { id: ownerId },
data: { modulePreferencesJson: { nutrients: true } },
});
return { ownerId };
},
body: () => ({ amountMl: 250, mode: "set", day: "2026-07-15" }),
ok: 200,
auditAction: "nutrient.water.write",
applied: async ({ ownerId }) =>
(
await getPrismaClient().nutrientIntakeDay.findFirst({
where: {
userId: ownerId,
day: "2026-07-15",
nutrient: "water",
source: "MANUAL",
},
})
)?.amount === 250,
});
proveManageRoute("PATCH /api/insights/patterns/[id]", {
route: "/api/insights/patterns/[id]",
action: "PATCH",
prepare: async (ownerId) =>
getPrismaClient().correlationPattern.create({
data: {
userId: ownerId,
canonicalKey: "mood:water",
family: "mood",
factorKey: "water",
outcomeKey: "mood",
lagDays: 0,
sampleSize: 12,
effectSize: 0.3,
pValue: 0.01,
evidenceHash: "evidence-v1",
lastComputedAt: new Date(ISO),
},
}),
params: (pattern) => ({ id: pattern.id }),
body: () => ({ dismissed: true }),
ok: 200,
auditAction: "correlationPattern.dismiss",
applied: async (pattern) =>
(
await getPrismaClient().correlationPattern.findUnique({
where: { id: pattern.id },
})
)?.dismissedAt !== null,
});
proveManageRoute("POST /api/export/health-record", {
route: "/api/export/health-record",
prepare: async (ownerId) => ({ ownerId }),
body: () => ({ format: "fhir", selection: { v: 2, leaves: ["MOOD"] } }),
ok: 200,
auditAction: "health-record.export",
applied: async ({ ownerId }) => {
const row = await getPrismaClient().auditLog.findFirst({
where: { userId: ownerId, action: "health-record.export" },
});
return row !== null;
},
});
/* -------------------------------------------------------------------------- */
/* 2 — reconstructable or refused */
/* -------------------------------------------------------------------------- */
describe("what a destruction leaves behind", () => {
it("[J2-manage-edits-without-settings] C3: a hard delete files what it destroyed, and never the encrypted text", async () => {
// The preventive-care reminder is the sharpest case in the tree: the row
// is gone, no restore route reaches it, and the audit row used to say
// `{ reminderId }` — a pointer at nothing. An owner reading their feed
// could see that a recall disappeared and never learn what it was for.
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const reminder = await makeReminder(owner.id);
await switchInto(owner.id, manager.id, "MANAGE");
const { DELETE } =
await import("@/app/api/measurement-reminders/[id]/route");
expect(
(
await call(
DELETE as Handler,
"DELETE",
`/api/measurement-reminders/${reminder.id}`,
undefined,
{ id: reminder.id },
)
).status,
).toBe(200);
const { details } = await auditRow("measurementReminder.delete");
expect(details.destroyedModel).toBe("MeasurementReminder");
expect(details.destroyedId).toBe(reminder.id);
expect(details.destroyedLabel).toBe("Colonoscopy recall");
expect(details.destroyedAt).toBe(reminder.nextDueAt?.toISOString());
expect(details.intervalDays).toBe(365);
});
it("C3: the allergy delete carries the allergen and never the reaction", async () => {
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const allergy = await getPrismaClient().allergy.create({
data: {
userId: owner.id,
substance: "Penicillin",
category: "MEDICATION",
type: "ALLERGY",
severity: "SEVERE",
status: "ACTIVE",
onsetAt: new Date("2020-03-01T00:00:00.000Z"),
},
});
await switchInto(owner.id, manager.id, "MANAGE");
const { DELETE } = await import("@/app/api/allergies/[id]/route");
expect(
(
await call(
DELETE as Handler,
"DELETE",
`/api/allergies/${allergy.id}`,
undefined,
{ id: allergy.id },
)
).status,
).toBe(200);
const { row, details } = await auditRow("allergy.delete");
expect(details.destroyedLabel).toBe("Penicillin");
expect(details.severity).toBe("SEVERE");
expect(details.destroyedAt).toBe("2020-03-01T00:00:00.000Z");
// …and the audit table did not become a second store for the encrypted
// half of the row. Asserted on the raw string, because a nested key would
// slip past a top-level property check.
expect(row.details ?? "").not.toContain("reaction");
});
it("[J2-manage-edits-without-settings] C4: an edit files the field family and the value that is gone", async () => {
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const measurement = await makeMeasurement(owner.id, 80);
await switchInto(owner.id, manager.id, "MANAGE");
const { PUT } = await import("@/app/api/measurements/[id]/route");
expect(
(
await call(
PUT as Handler,
"PUT",
`/api/measurements/${measurement.id}`,
{ value: 74.5 },
{ id: measurement.id },
)
).status,
).toBe(200);
const { details } = await auditRow("measurement.update");
expect(details.fields).toEqual(["value"]);
expect((details.previous as Record<string, unknown>).value).toBe(80);
});
it("a restore puts back the row it names, and leaves the others tombstoned", async () => {
// The fake-that-ignores-its-`where` case: a count check passes on a
// restore that clears every tombstone in the record. The id is what
// defeats it.
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const kept = await makeMeasurement(owner.id, 81);
const other = await makeMeasurement(owner.id, 82);
await getPrismaClient().measurement.updateMany({
where: { id: { in: [kept.id, other.id] } },
data: { deletedAt: new Date() },
});
await switchInto(owner.id, manager.id, "MANAGE");
const { POST } = await import("@/app/api/measurements/restore/route");
const response = await call(
POST as Handler,
"POST",
"/api/measurements/restore",
{
ids: [kept.id],
},
);
expect(response.status).toBe(200);
const restored = await getPrismaClient().measurement.findUnique({
where: { id: kept.id },
});
const untouched = await getPrismaClient().measurement.findUnique({
where: { id: other.id },
});
expect(restored?.deletedAt).toBeNull();
expect(untouched?.deletedAt).not.toBeNull();
const { row } = await auditRow("measurement.restore");
expect(row.userId).toBe(owner.id);
expect(row.actorUserId).toBe(manager.id);
});
});
/* -------------------------------------------------------------------------- */
/* 3 — the conditions that are not about the audit row */
/* -------------------------------------------------------------------------- */
describe("the conditions the admissions were granted on", () => {
it("C1: the manager burns their own rate allowance, not the owner's", async () => {
// The buckets live in Postgres, so the key itself is readable. A bucket
// keyed on the record would let a manager lock the owner out of their own
// restore button by hammering it.
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const row = await makeMeasurement(owner.id);
await getPrismaClient().measurement.update({
where: { id: row.id },
data: { deletedAt: new Date() },
});
await switchInto(owner.id, manager.id, "MANAGE");
const { POST } = await import("@/app/api/measurements/restore/route");
expect(
(
await call(POST as Handler, "POST", "/api/measurements/restore", {
ids: [row.id],
})
).status,
).toBe(200);
const buckets = await getPrismaClient().rateLimit.findMany();
expect(buckets.length).toBeGreaterThan(0);
expect(buckets.some((b) => b.key.includes(manager.id))).toBe(true);
expect(buckets.some((b) => b.key.includes(owner.id))).toBe(false);
});
it("C5: a manager's navigation enqueues no generation; the owner's does", async () => {
// Both halves, and the second is what makes the first mean anything: the
// narrative route warms unconditionally, so a zero on the delegated side
// could just as easily mean the route never reached the enqueue at all.
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const { GET } = await import("@/app/api/insights/narrative/route");
await signIn(owner.id);
expect(
(await call(GET as Handler, "GET", "/api/insights/narrative?period=week"))
.status,
).toBe(200);
expect(enqueueNarrativeWarm).toHaveBeenCalledTimes(1);
enqueueNarrativeWarm.mockClear();
await switchInto(owner.id, manager.id, "MANAGE");
const delegated = await call(
GET as Handler,
"GET",
"/api/insights/narrative?period=week",
);
expect(delegated.status).toBe(200);
expect(enqueueNarrativeWarm).not.toHaveBeenCalled();
});
it("C5: a WRITE grant cannot read the generated surfaces at all", async () => {
const owner = await makeUser("owner");
const helper = await makeUser("helper");
await switchInto(owner.id, helper.id, "WRITE");
const { GET } = await import("@/app/api/insights/narrative/route");
await expectDenied(
await call(GET as Handler, "GET", "/api/insights/narrative?period=week"),
);
expect(enqueueNarrativeWarm).not.toHaveBeenCalled();
});
it("C6: the delegated mood create refuses the sync client's externalId", async () => {
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const body = {
mood: "GUT",
moodLoggedAt: ISO,
// A stable, device-shaped id: the floor on `externalId` refuses an
// unstable one at the schema, and this test is about the level, not
// about that floor.
externalId: "8B0F3C2E-1D4A-4F5B-9C6D-7E8F90A1B2C3",
source: "MOODLOG",
};
await switchInto(owner.id, manager.id, "MANAGE");
const { POST } = await import("@/app/api/mood-entries/route");
const refused = await call(
POST as Handler,
"POST",
"/api/mood-entries",
body,
);
expect(refused.status).toBe(422);
expect((await payload(refused)).meta?.errorCode).toBe(
"mood.create.external_id_not_delegable",
);
expect(
await getPrismaClient().moodEntry.count({ where: { userId: owner.id } }),
).toBe(0);
// The same body from the owner's own session still lands: C6 closes the
// delegated branch, not the route.
await signIn(owner.id);
const accepted = await call(
POST as Handler,
"POST",
"/api/mood-entries",
body,
);
expect(accepted.status).toBe(201);
expect(
await getPrismaClient().moodEntry.count({ where: { userId: owner.id } }),
).toBe(1);
});
});
describe("the complete MANAGE handler matrix", () => {
it("registers one strict actor-and-effect driver for every admission", () => {
const expected = ADMITTED_MUTATING_HANDLERS.map(matrixKey).sort();
expect(STRICT_DRIVER_KEYS.size).toBe(89);
expect([...STRICT_DRIVER_KEYS].sort()).toEqual(expected);
});
it("executes every registered driver through its owned effect and actor audit", () => {
const expected = ADMITTED_MUTATING_HANDLERS.map(matrixKey).sort();
expect(REAL_EFFECT_KEYS.size).toBe(89);
expect([...REAL_EFFECT_KEYS].sort()).toEqual(expected);
});
});